draggable
This commit is contained in:
parent
69930bd9df
commit
a6a7ea6c8c
13 changed files with 420 additions and 162 deletions
Binary file not shown.
|
|
@ -198,7 +198,10 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
|||
final _undoableIndex = Undoable<int?>(null);
|
||||
final Queue<CardSwiperDirection> _directionHistory = Queue();
|
||||
|
||||
int? get _currentIndex => _undoableIndex.state;
|
||||
bool usePrevIndex = false;
|
||||
|
||||
int? get _currentIndex =>
|
||||
usePrevIndex ? _undoableIndex.previousState : _undoableIndex.state;
|
||||
|
||||
int? get _nextIndex => getValidIndexOffset(1);
|
||||
|
||||
|
|
@ -319,13 +322,13 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
|||
onPanUpdate: (tapInfo) {
|
||||
if (!widget.isDisabled &&
|
||||
tapInfo.delta.dx.abs() > 2 * tapInfo.delta.dy.abs()) {
|
||||
setState(
|
||||
() => _cardAnimation.update(
|
||||
setState(() {
|
||||
_cardAnimation.update(
|
||||
tapInfo.delta.dx,
|
||||
tapInfo.delta.dy,
|
||||
_tappedOnTop,
|
||||
),
|
||||
);
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
onPanEnd: (tapInfo) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:mnemo_cards/features/packs/preview_packs_poller.dart';
|
||||
import 'package:mnemo_cards/features/tests/test_state_holder.dart';
|
||||
import 'package:mnemo_cards/features/tests/test_widgets/test_image.dart';
|
||||
import 'package:mnemo_cards/features/packs/pack_cache_manager.dart';
|
||||
import 'package:mnemo_cards/main.dart';
|
||||
import 'package:mnemo_cards/theme/themes.dart';
|
||||
import 'package:mnemo_cards/utils/iterable_helper.dart';
|
||||
import 'package:mnemo_cards/utils/string_helper.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
|
@ -56,7 +55,22 @@ class TestManager extends ChangeNotifier {
|
|||
(tests) async => pollTests(packId: packId)
|
||||
.catchError((_) => Future.value(<TestDto>[])),
|
||||
)
|
||||
.where((test) => test.isNotEmpty);
|
||||
.where((test) => test.isNotEmpty)
|
||||
.distinct((p, n) {
|
||||
if (p.length == n.length) {
|
||||
for (int i = 0; i < p.length; i++) {
|
||||
if (p[i].id != n[i].id ||
|
||||
p[i].version != n[i].version ||
|
||||
!ListEquality().equals(
|
||||
p[i].statistics?.words.words,
|
||||
n[i].statistics?.words.words,
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
TestQuestionState? getState(int id) => testHolder.testQuestionState(id);
|
||||
|
|
|
|||
|
|
@ -66,10 +66,10 @@ class _TestProgressWidgetState extends State<TestProgressWidget> {
|
|||
super.initState();
|
||||
locator.testManager.pageController.removeListener(_updateDataListener);
|
||||
locator.testManager.pageController.addListener(_updateDataListener);
|
||||
timer = Stream.periodic(
|
||||
Duration(seconds: 1),
|
||||
(tick) => Duration(seconds: 300 - tick - 1),
|
||||
).startWith(Duration(seconds: 300)).take(300);
|
||||
// timer = Stream.periodic(
|
||||
// Duration(seconds: 1),
|
||||
// (tick) => Duration(seconds: 300 - tick - 1),
|
||||
// ).startWith(Duration(seconds: 300)).take(300);
|
||||
_updateData();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ extension NullableIterableHelper<T> on Iterable<T?> {
|
|||
}
|
||||
|
||||
extension ListHelper<T> on List<T> {
|
||||
|
||||
// List<T> shuffle([int? seed]) {
|
||||
// Random r = Random(seed);
|
||||
// var items = [...this];
|
||||
|
|
@ -26,4 +25,37 @@ extension ListHelper<T> on List<T> {
|
|||
// return items;
|
||||
// }
|
||||
|
||||
}
|
||||
void move(int from, int to) {
|
||||
RangeError.checkValidIndex(from, this, "from", length);
|
||||
RangeError.checkValidIndex(to, this, "to", length);
|
||||
var element = this[from];
|
||||
if (from < to) {
|
||||
this.setRange(from, to, this, from + 1);
|
||||
} else {
|
||||
this.setRange(to + 1, from + 1, this, to);
|
||||
}
|
||||
this[to] = element;
|
||||
}
|
||||
|
||||
void sortInOrder<P>(List<P> order, P valueGetter(T v)) => sort((p, n) {
|
||||
final pVal = valueGetter(p);
|
||||
final nVal = valueGetter(n);
|
||||
final pIndex = order.indexOf(pVal);
|
||||
final nIndex = order.indexOf(nVal);
|
||||
if (pIndex == -1) {
|
||||
return nIndex == -1
|
||||
? () {
|
||||
if (pVal is num) {
|
||||
return pVal.compareTo(nVal as num);
|
||||
} else if (pVal is String) {
|
||||
return pVal.compareTo(nVal as String);
|
||||
}
|
||||
return pVal.toString().compareTo(nVal.toString());
|
||||
}.call()
|
||||
: 1;
|
||||
} else if (nIndex == -1) {
|
||||
return -1;
|
||||
}
|
||||
return pIndex.compareTo(nIndex);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import '../../features/card_flipper/flip_card_controllers.dart';
|
|||
import '../../features/card_swiper/src/allowed_swipe_direction.dart';
|
||||
import '../../features/card_swiper/src/card_swiper.dart';
|
||||
import '../../features/card_swiper/src/card_swiper_controller.dart';
|
||||
import '../../features/card_swiper/src/enums.dart';
|
||||
import '../../main.dart';
|
||||
import '../game_card_widget.dart';
|
||||
|
||||
|
|
@ -84,9 +85,7 @@ class CardGameMainWidget extends StatelessWidget {
|
|||
cardsCount: length,
|
||||
numberOfCardsDisplayed: numberOfCardsDisplayed,
|
||||
allowedSwipeDirection:
|
||||
AllowedSwipeDirection.symmetric(
|
||||
horizontal: true,
|
||||
),
|
||||
AllowedSwipeDirection.all(),
|
||||
scale: initialScale,
|
||||
backCardOffset: Offset(0, cardHeight * 0.1 / 2),
|
||||
isLoop: false,
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ class _CardGamePageState extends State<CardGamePage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// todo move to controller
|
||||
final timer = Stream.periodic(Duration(seconds: 1), (tick) {
|
||||
return Duration(seconds: tick + 1);
|
||||
}).asBroadcastStream().take(3600).startWith(Duration.zero);
|
||||
// final timer = Stream.periodic(Duration(seconds: 1), (tick) {
|
||||
// return Duration(seconds: tick + 1);
|
||||
// }).asBroadcastStream().take(3600).startWith(Duration.zero);
|
||||
|
||||
return Scaffold(
|
||||
body: PopScope(
|
||||
|
|
@ -96,7 +96,6 @@ class _CardGamePageState extends State<CardGamePage> {
|
|||
),
|
||||
shuffledCards.length,
|
||||
widget.color ?? borderGray,
|
||||
timer: timer,
|
||||
popText: widget.infinite ? 'к теме' : null,
|
||||
key: ValueKey('CARDS PROGRESS'),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@ import 'package:collection/collection.dart';
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards/main.dart';
|
||||
import 'package:mnemo_cards/utils/iterable_helper.dart';
|
||||
import 'package:mnemo_cards/widgets/fade_in_widget.dart';
|
||||
import 'package:mnemo_cards/widgets/feedback_builder.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:reorderables/reorderables.dart';
|
||||
import 'package:yandex_mobileads/mobile_ads.dart';
|
||||
import '../../domain/router/app_router.gr.dart';
|
||||
import '../../features/analytics/analytics.dart';
|
||||
|
|
@ -21,6 +25,7 @@ import 'cards_view.dart';
|
|||
import 'cards_view_controller.dart';
|
||||
|
||||
final _mainScrollController = ScrollController();
|
||||
final _reorderableTests = GlobalKey();
|
||||
|
||||
class AvailablePack extends StatelessWidget {
|
||||
final CardPackDto cardPack;
|
||||
|
|
@ -65,24 +70,20 @@ class AvailablePack extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
StreamBuilder(
|
||||
stream: _cardsViewController.asStream,
|
||||
StreamBuilder<bool>(
|
||||
stream: _cardsViewController.asStream
|
||||
.map((s) => s.expanded),
|
||||
builder: (context, snapshot) {
|
||||
return AnimatedRotation(
|
||||
duration: Duration(milliseconds: 300),
|
||||
turns: (snapshot.data?.expanded ?? false)
|
||||
? 0.5
|
||||
: 0.0,
|
||||
child: _Button(
|
||||
'down.png',
|
||||
() async {
|
||||
if (snapshot.hasData) {
|
||||
_cardsViewController.setExpanded(
|
||||
!snapshot.requireData.expanded,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
return _DownButton(
|
||||
!(snapshot.data ?? false),
|
||||
() async {
|
||||
if (snapshot.hasData) {
|
||||
_cardsViewController.setExpanded(
|
||||
!snapshot.requireData,
|
||||
);
|
||||
}
|
||||
},
|
||||
key: ValueKey('DownButton'),
|
||||
);
|
||||
}),
|
||||
_Button('shuffle.png', () async {
|
||||
|
|
@ -168,16 +169,78 @@ class _TestsSection extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final order = (globalSharedPreferences
|
||||
.getString('tests_order_${packId}')
|
||||
?.decodeList<String>() ??
|
||||
<String>[])
|
||||
.toList();
|
||||
|
||||
void saveOrder() {
|
||||
globalSharedPreferences.setString(
|
||||
'tests_order_${packId}',
|
||||
order.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
return StreamBuilder(
|
||||
stream: locator.testManager.loadTestsStream(packId: packId),
|
||||
builder: (context, snapshot) {
|
||||
return Column(
|
||||
children: [
|
||||
if (snapshot.hasData)
|
||||
...snapshot.data!.mapIndexed(
|
||||
(int index, TestDto dto) => FadeInWidget(
|
||||
delay: Duration(milliseconds: index * 200),
|
||||
animationDuration: const Duration(milliseconds: 200),
|
||||
final tests = snapshot.data ?? [];
|
||||
if (tests.isEmpty) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (order.isEmpty) {
|
||||
order.addAll(tests.map((t) => t.name));
|
||||
saveOrder();
|
||||
}
|
||||
return FadeInWidget(
|
||||
animationDuration: Duration(milliseconds: 400),
|
||||
child: StatefulBuilder(builder: (context, setState) {
|
||||
tests..sortInOrder(order, (t) => t.name);
|
||||
return ReorderableListView.builder(
|
||||
key: _reorderableTests,
|
||||
itemExtent: 130.h + 2 * 5.h,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10.w,
|
||||
vertical: 5.h,
|
||||
),
|
||||
shrinkWrap: true,
|
||||
scrollController: _mainScrollController,
|
||||
// physics: NeverScrollableScrollPhysics(),
|
||||
proxyDecorator: (_, index, animation) {
|
||||
final dto = tests[index];
|
||||
return Material(
|
||||
elevation: 5,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.transparent,
|
||||
child: _TestButton(
|
||||
title: dto.name,
|
||||
subtitle: null,
|
||||
stats: dto.statistics,
|
||||
color: dto.color?.asColor ?? borderGray,
|
||||
time: dto.time,
|
||||
timeSubtitle: dto.timeSubtitle,
|
||||
onTap: () {},
|
||||
),
|
||||
);
|
||||
},
|
||||
onReorder: (int from, int to) {
|
||||
if (to - from > 1) {
|
||||
to--;
|
||||
}
|
||||
order.move(from, to);
|
||||
saveOrder();
|
||||
setState(() {});
|
||||
},
|
||||
itemCount: tests.length,
|
||||
itemBuilder: (context, index) {
|
||||
final dto = tests[index];
|
||||
return Padding(
|
||||
key: ValueKey(dto.id),
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 5.0.h,
|
||||
),
|
||||
child: _TestButton(
|
||||
title: dto.name,
|
||||
subtitle: null,
|
||||
|
|
@ -192,9 +255,10 @@ class _TestsSection extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -261,6 +325,40 @@ class _Button extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _DownButton extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final bool isDown;
|
||||
|
||||
_DownButton(bool this.isDown, this.onTap, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: 110.w,
|
||||
height: 60.h,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: borderGray),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedRotation(
|
||||
duration: Duration(milliseconds: 300),
|
||||
turns: isDown ? 0.0 : 0.5,
|
||||
child: Image.asset(
|
||||
'icons/down.png',
|
||||
width: 29.w,
|
||||
// height: 29.h,
|
||||
fit: BoxFit.scaleDown,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TestButton extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final String title;
|
||||
|
|
@ -283,99 +381,94 @@ class _TestButton extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 5.0.h,
|
||||
horizontal: 10.w,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 120.h,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: borderGray),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0.h,
|
||||
left: 10.0.w,
|
||||
right: 10.0.w,
|
||||
bottom: 20.h,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 25.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 0.85,
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 130.h,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: borderGray),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0.h,
|
||||
left: 10.0.w,
|
||||
right: 10.0.w,
|
||||
bottom: 20.h,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 25.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 0.85,
|
||||
),
|
||||
),
|
||||
if (subtitle != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w300,
|
||||
height: 0.85,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (subtitle != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w300,
|
||||
height: 0.85,
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
if (stats != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 50.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: HorizontalProgressWidget(
|
||||
stats!.words.incorrect.round(),
|
||||
stats!.words.total.round(),
|
||||
color,
|
||||
hasBorder: true,
|
||||
),
|
||||
Spacer(),
|
||||
if (stats != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 50.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: HorizontalProgressWidget(
|
||||
stats!.words.incorrect.round(),
|
||||
stats!.words.total.round(),
|
||||
color,
|
||||
hasBorder: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: 75.w,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (time != null)
|
||||
Text(
|
||||
time!,
|
||||
style: TextStyle(
|
||||
fontSize: 40.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 0.85),
|
||||
),
|
||||
if (timeSubtitle != null)
|
||||
Text(
|
||||
timeSubtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 24.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 0.85),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 75.w,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (time != null)
|
||||
Text(
|
||||
time!,
|
||||
style: TextStyle(
|
||||
fontSize: 40.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 0.85),
|
||||
),
|
||||
if (timeSubtitle != null)
|
||||
Text(
|
||||
timeSubtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 24.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 0.85),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,27 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards/features/packs/images_holder.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:reorderables/reorderables.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../../di/locator.dart';
|
||||
import '../../utils/string_helper.dart';
|
||||
import '../../domain/router/app_router.gr.dart';
|
||||
import '../../main.dart';
|
||||
import '../feedback_builder.dart';
|
||||
import '../game_card_widget.dart';
|
||||
import 'cards_view_controller.dart';
|
||||
|
||||
class CardsView extends StatefulWidget {
|
||||
final CardPackDto cardPack;
|
||||
final CardsViewController controller;
|
||||
final List<int> cardsOrder = [];
|
||||
|
||||
CardsView(this.controller, this.cardPack);
|
||||
|
||||
|
|
@ -150,34 +154,42 @@ class _CardsViewState extends State<CardsView>
|
|||
((packCards.length / 3).ceil() * 4 / 3 * cardWidth -
|
||||
cardWidth * 4 / 3) *
|
||||
animation.value,
|
||||
child: Wrap(
|
||||
key: wrapKey,
|
||||
children: List.generate(packCards.length, (index) {
|
||||
final card = packCards[index];
|
||||
final memoryImage = card.memoryImage;
|
||||
if (memoryImage == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
return Container(
|
||||
height: cardWidth * 4 / 3,
|
||||
width: cardWidth,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: cardWidth - imageWidth - 1,
|
||||
vertical: cardWidth - imageWidth - 1,
|
||||
),
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: imageWidth * 4 / 3,
|
||||
child: _CardBuilder(
|
||||
cardPack,
|
||||
card,
|
||||
memoryImage,
|
||||
(card) => onCardTap(packCards, card),
|
||||
child: Builder(builder: (context) {
|
||||
return ReorderableWrap(
|
||||
key: wrapKey,
|
||||
onReorder: (int from, int to) {
|
||||
controller.onOrderChanged(from, to);
|
||||
},
|
||||
buildDraggableFeedback: FeedbackBuilder.simple,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: (cardWidth - imageWidth - 1),
|
||||
vertical: (cardWidth - imageWidth - 1),
|
||||
),
|
||||
runSpacing: (cardWidth - imageWidth - 1) * 2,
|
||||
spacing: (cardWidth - imageWidth - 1) * 2,
|
||||
children: List.generate(packCards.length, (index) {
|
||||
final card = packCards[index];
|
||||
final memoryImage = card.memoryImage;
|
||||
if (memoryImage == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
return Container(
|
||||
height: 2 * imageWidth - cardWidth * 2 / 3 + 2,
|
||||
width: 2 * imageWidth - cardWidth + 2,
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: imageWidth * 4 / 3,
|
||||
child: _CardBuilder(
|
||||
cardPack,
|
||||
card,
|
||||
memoryImage,
|
||||
(card) => onCardTap(packCards, card),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import 'dart:math';
|
|||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:mnemo_cards/features/packs/images_holder.dart';
|
||||
import 'package:mnemo_cards/main.dart';
|
||||
import 'package:mnemo_cards/utils/iterable_helper.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
|
|
@ -13,6 +15,10 @@ class CardsViewController extends ChangeNotifier {
|
|||
bool _favorite = false;
|
||||
double imageWidth = 0.0;
|
||||
List<GameCardDto> cards = [];
|
||||
List<int> allCardsOrder = [];
|
||||
List<int> favoriteCardsOrder = [];
|
||||
|
||||
List<int> get cardsOrder => _favorite ? favoriteCardsOrder : allCardsOrder;
|
||||
|
||||
final CardPackDto cardPack;
|
||||
|
||||
|
|
@ -57,6 +63,61 @@ class CardsViewController extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _saveCardsOrder() async {
|
||||
if (_favorite) {
|
||||
await globalSharedPreferences.setString(
|
||||
'sort_fav_${cardPack.id}',
|
||||
favoriteCardsOrder.encode(),
|
||||
);
|
||||
} else {
|
||||
await globalSharedPreferences.setString(
|
||||
'sort_${cardPack.id}',
|
||||
allCardsOrder.encode(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _loadCardsOrder() {
|
||||
favoriteCardsOrder = globalSharedPreferences
|
||||
.getString(
|
||||
'sort_fav_${cardPack.id}',
|
||||
)
|
||||
?.decodeList<int>() ??
|
||||
[];
|
||||
allCardsOrder = globalSharedPreferences
|
||||
.getString(
|
||||
'sort_${cardPack.id}',
|
||||
)
|
||||
?.decodeList<int>() ??
|
||||
[];
|
||||
if (allCardsOrder.isEmpty) {
|
||||
allCardsOrder = cardPack.cards.map((c) => c.id).toList();
|
||||
}
|
||||
if (favoriteCardsOrder.isEmpty) {
|
||||
favoriteCardsOrder = allCardsOrder.toList();
|
||||
}
|
||||
}
|
||||
|
||||
void setCardsOrder(List<int> order) {
|
||||
if (_favorite) {
|
||||
favoriteCardsOrder = order.toList();
|
||||
} else {
|
||||
allCardsOrder = order.toList();
|
||||
}
|
||||
_saveCardsOrder();
|
||||
_updateCards();
|
||||
}
|
||||
|
||||
void onOrderChanged(int from, int to) {
|
||||
if (_favorite) {
|
||||
favoriteCardsOrder.move(from, to);
|
||||
} else {
|
||||
allCardsOrder.move(from, to);
|
||||
}
|
||||
_saveCardsOrder();
|
||||
_updateCards();
|
||||
}
|
||||
|
||||
void setFavorite(bool favorite) {
|
||||
_favorite = favorite;
|
||||
_updateCards();
|
||||
|
|
@ -75,11 +136,23 @@ class CardsViewController extends ChangeNotifier {
|
|||
|
||||
CardsViewController(this.cardPack) {
|
||||
addListener(_addToStream);
|
||||
_loadCardsOrder();
|
||||
_updateCards();
|
||||
}
|
||||
|
||||
void _updateCards() {
|
||||
cards = _filterPackCards();
|
||||
cards
|
||||
..sort((p, n) {
|
||||
final pIndex = cardsOrder.indexOf(p.id);
|
||||
final nIndex = cardsOrder.indexOf(n.id);
|
||||
if (pIndex == -1) {
|
||||
return nIndex == -1 ? p.id.compareTo(n.id) : 1;
|
||||
} else if (nIndex == -1) {
|
||||
return -1;
|
||||
}
|
||||
return pIndex.compareTo(nIndex);
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class FadeInWidget extends StatelessWidget {
|
|||
this.delay = const Duration(milliseconds: 200),
|
||||
this.future,
|
||||
this.animationDuration = const Duration(milliseconds: 200),
|
||||
super.key,
|
||||
}) : assert(future != null || delay != null);
|
||||
|
||||
@override
|
||||
|
|
|
|||
31
lib/widgets/feedback_builder.dart
Normal file
31
lib/widgets/feedback_builder.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class FeedbackBuilder {
|
||||
static Widget tests(Widget child, int index, Animation<double> animation) =>
|
||||
Transform(
|
||||
transform: new Matrix4.rotationZ(0),
|
||||
child: Material(
|
||||
child: child,
|
||||
elevation: 1.0,
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
);
|
||||
|
||||
static Widget simple(
|
||||
BuildContext context,
|
||||
BoxConstraints constraints,
|
||||
Widget child,
|
||||
) {
|
||||
return Transform(
|
||||
transform: new Matrix4.rotationZ(0.05),
|
||||
// alignment: FractionalOffset.topLeft,
|
||||
child: Material(
|
||||
child: child,
|
||||
elevation: 6.0,
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -168,6 +168,7 @@ class _PackButton4 extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(4.0.h),
|
||||
alignment: Alignment.center,
|
||||
|
|
|
|||
Loading…
Reference in a new issue