mnemo_cards/lib/admin/all_cards.dart

368 lines
12 KiB
Dart
Raw Normal View History

2024-05-22 20:48:44 +00:00
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 {
2024-06-21 19:12:29 +00:00
final ids = loadingIds.take(10).toList();
2024-05-22 20:48:44 +00:00
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,
),
),
]),
);
}
}