import 'dart:convert'; import 'dart:math'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:mnemo_cards/admin/add_card.dart'; import 'package:mnemo_cards/admin/admin_api.dart'; import 'package:mnemo_cards/di/injector.dart'; import 'package:mnemo_cards/managers/repository/dio_provider.dart'; import 'package:mnemo_cards/theme/themes.dart'; import 'package:mnemo_cards/widgets/header.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import '../main.dart'; import 'add_package.dart'; class AllCards extends StatefulWidget { @override State createState() => _AllCardsState(); const AllCards(); } class _AllCardsState extends State { static List ids = []; static Set cards = {}; static String filter = ''; static List _packPreviews = []; static EditCardPackDto? _currentPack; static String? _currentId; static List _selectedCards = []; static Map _cache = {}; bool get selectingMode => _selectedCards.isNotEmpty; Dio get dio => getIt.get().dio; AdminApi get api => getIt.get(); Future loadCards() async { var loadingIds = ids.toList(); cards.clear(); _cache.clear(); while (loadingIds.isNotEmpty) { try { final ids = loadingIds.take(10).toList(); loadingIds.removeRange(0, ids.length); final cardsPage = await api.getCards(ids); for (final card in cardsPage) { final imageString = card.image?.substring(0, min(100, card.image!.length)) ?? ''; _cache[card.id.toString()] = '${card.id}${card.mnemo}${card.transcription}${card.transcriptionMnemo}${card.original}${card.translation}${imageString}'; } cards.addAll(cardsPage); setState(() {}); } catch (e) { print(e); } } } Future loadIds() async { ids = await api.getCardIds(); setState(() {}); } Future loadPacks() async { _packPreviews = await api.packs(); clearCurrentPack(); } Future loadCurrentPack() async { if (_currentId != null) { _currentPack = await api.getEditPack(_currentId!); setState(() {}); } } void clearCurrentPack() { _currentId = null; _currentPack = null; setState(() {}); } Future editPack(EditCardPackDto pack) async { final result = await api.editPack(pack); if (!result) { ScaffoldMessenger.of(scaffoldKey.currentContext!).showSnackBar(SnackBar( content: Text('ERROR'), )); } else { ScaffoldMessenger.of(scaffoldKey.currentContext!).showSnackBar(SnackBar( content: Text('Success!!!'), )); loadCurrentPack(); _selectedCards.clear(); } } void addOrEditPack() => AddPackage(_currentPack, api).addPack(context); List buildCards() { final filteredCards = cards.where((dto) { final filterOk = filter.isEmpty || _cache[dto.id.toString()]?.contains(filter) == true; final packFilter = _currentPack?.addCardIds?.contains(dto.id.toString()) ?? true; return filterOk && packFilter; }); return filteredCards.toList(); } @override Widget build(BuildContext context) { final filteredCards = buildCards(); return Scaffold( body: SafeArea( child: Stack( children: [ ListView.builder( itemExtentBuilder: (index, dims) => index >= 1 ? 160.h : 350, itemBuilder: (context, index) { if (index == 0) return Column( children: [ Header( _currentId == null ? 'Все карточки' : _currentPack?.title ?? 'Пак ${_currentPack?.id}', subtitle: filteredCards.isEmpty ? 'Нет карточек' : '${filteredCards.length} карточек', hasPopButton: true, trail: IconButton( onPressed: () async { setState(() {}); await loadIds(); await loadPacks(); await loadCards(); }, icon: Icon(Icons.sync)), ), dropdownButton(context), Padding( padding: const EdgeInsets.all(8.0), child: TextField( decoration: InputDecoration(hintText: 'Поиск'), onChanged: (v) { if (filter != v.toLowerCase()) { filter = v.toLowerCase(); setState(() {}); } }, ), ), Expanded( child: GestureDetector( onTap: () => EditCard(null, api).addCard(context), child: Container( color: Colors.green.withOpacity(0.1), alignment: Alignment.center, child: Icon(Icons.add, size: 30), ), ), ), ], ); if (filteredCards.length >= (index - 1) * 3) { final cardIndex = (index - 1) * 3; return Row( children: [ if (filteredCards.length > cardIndex) cardWidget(filteredCards[cardIndex]), if (filteredCards.length > cardIndex + 1) cardWidget(filteredCards[cardIndex + 1]), if (filteredCards.length > cardIndex + 2) cardWidget(filteredCards[cardIndex + 2]), ], ); } return null; }), if (selectingMode) Align( alignment: Alignment.bottomCenter, child: Container( height: 100, padding: EdgeInsets.all(10.0), alignment: Alignment.center, child: Row( children: [ GestureDetector( onTap: () { _selectedCards.clear(); setState(() {}); }, child: Container( color: Colors.blue, padding: EdgeInsets.all(8.0), child: Row( children: [ Text('${_selectedCards.length} '), Icon(Icons.clear) ], ), ), ), if (_currentPack != null) if (_selectedCards.every((c) => _currentPack!.addCardIds?.contains(c) ?? false)) Row( children: [ SizedBox(width: 8.0), GestureDetector( onTap: () { final updated = _currentPack!.copyWith.removeCardIds( _selectedCards.toList(), ); editPack(updated); }, child: Container( padding: EdgeInsets.all(8.0), color: Colors.blue, child: Text('Убрать из пака'), ), ), SizedBox(width: 8.0), GestureDetector( onTap: () { final updated = _currentPack!.copyWith.previewCards( _selectedCards.toList(), ); editPack(updated); }, child: Container( padding: EdgeInsets.all(8.0), color: Colors.blue, child: Text('Как превью'), ), ), ], ) else MaterialButton( onPressed: () { final updated = _currentPack!.copyWith.addCardIds( _selectedCards.toList(), ); editPack(updated); }, child: Container( padding: EdgeInsets.all(8.0), color: Colors.blue, child: Text('Добавить в пак'), ), ) ], ), ), ), ], ), ), ); } Widget cardWidget(GameCardDto card) { void toggleSelect() { final id = card.id.toString(); if (_selectedCards.contains(id)) { _selectedCards.remove(id); } else { _selectedCards.add(card.id.toString()); } setState(() {}); } return GestureDetector( onTap: () { if (selectingMode) { toggleSelect(); } else { EditCard(card, api).addCard(context); } }, onLongPress: toggleSelect, child: _Card(card, api, _selectedCards.contains(card.id.toString())), ); } void _onPackChanged(String? id) { if (id == null) { clearCurrentPack(); } else { _currentId = id; loadCurrentPack(); } } Widget dropdownButton(BuildContext context) { return Row( children: [ Expanded( child: DropdownButton( items: [..._packPreviews, null] .map((p) => DropdownMenuItem( child: Padding( padding: const EdgeInsets.all(8.0), child: p == null ? Text('Все паки') : Text('${p.title} ${p.cards}'), ), value: p?.id, )) .toList(), value: _currentId, onChanged: (v) => _onPackChanged(v), ), ), IconButton( onPressed: addOrEditPack, icon: _currentPack == null ? Icon(Icons.add) : Icon(Icons.edit), ), ], ); } } class _Card extends StatelessWidget { final GameCardDto cardDto; final AdminApi api; final bool selected; _Card(this.cardDto, this.api, this.selected); @override Widget build(BuildContext context) { final image = cardDto.image != null ? base64.decode(cardDto.image!) : null; return Container( padding: EdgeInsets.all(8.0), width: MediaQuery.of(context).size.width / 3, height: 160.h, decoration: BoxDecoration( border: Border.all(color: selected ? Colors.blue : borderGray), color: selected ? Colors.blue.withOpacity(0.2) : null, ), child: Column(children: [ Text(cardDto.translation ?? ''), Text(cardDto.original ?? ''), if (image != null) Expanded( child: Image.memory( key: ObjectKey(cardDto.image), gaplessPlayback: true, image, fit: BoxFit.scaleDown, ), ), ]), ); } }