mnemo_cards/lib/admin/all_cards.dart
2024-05-31 17:35:41 +03:00

367 lines
12 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: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<StatefulWidget> createState() => _AllCardsState();
const AllCards();
}
class _AllCardsState extends State<AllCards> {
static List<String> ids = [];
static Set<GameCardDto> cards = {};
static String filter = '';
static List<CardPackPreviewDto> _packPreviews = [];
static EditCardPackDto? _currentPack;
static String? _currentId;
static List<String> _selectedCards = [];
static Map<String, String> _cache = {};
bool get selectingMode => _selectedCards.isNotEmpty;
Dio get dio => getIt.get<DioProvider>().dio;
AdminApi get api => getIt.get<AdminApi>();
Future<void> loadCards() async {
var loadingIds = ids.toList();
cards.clear();
_cache.clear();
while (loadingIds.isNotEmpty) {
try {
final ids = loadingIds.take(20).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<void> loadIds() async {
ids = await api.getCardIds();
setState(() {});
}
Future<void> loadPacks() async {
_packPreviews = await api.packs();
clearCurrentPack();
}
Future<void> loadCurrentPack() async {
if (_currentId != null) {
_currentPack = await api.getEditPack(_currentId!);
setState(() {});
}
}
void clearCurrentPack() {
_currentId = null;
_currentPack = null;
setState(() {});
}
Future<void> 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<GameCardDto> 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<String?>(
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,
),
),
]),
);
}
}