This commit is contained in:
Dmitry 2024-06-09 02:34:38 +03:00
parent 7ac591172b
commit c4c42e4af5
27 changed files with 435 additions and 236 deletions

View file

@ -25,11 +25,11 @@ class AppRouter extends $AppRouter {
transitionsBuilder: TransitionsBuilders.noTransition, transitionsBuilder: TransitionsBuilders.noTransition,
durationInMilliseconds: 200, durationInMilliseconds: 200,
), ),
CustomRoute( // CustomRoute(
page: ExplorePage.page, // page: ExplorePage.page,
transitionsBuilder: TransitionsBuilders.noTransition, // transitionsBuilder: TransitionsBuilders.noTransition,
durationInMilliseconds: 200, // durationInMilliseconds: 200,
), // ),
// CustomRoute( // CustomRoute(
// page: ProfilePage.page, // page: ProfilePage.page,
// transitionsBuilder: TransitionsBuilders.noTransition, // transitionsBuilder: TransitionsBuilders.noTransition,
@ -71,6 +71,13 @@ class AppRouter extends $AppRouter {
} }
} }
static void closeAuthPage() {
final router = main.appRouter;
if (router.isRouteActive(AuthPage.name)) {
router.replace(PageRouteInfo(HomePage.name));
}
}
static void openAuthOrProfile() { static void openAuthOrProfile() {
if (locator.userManager.hasUser) { if (locator.userManager.hasUser) {
openProfile(); openProfile();

View file

@ -8,35 +8,33 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:ui' as _i11; import 'dart:ui' as _i10;
import 'package:auto_route/auto_route.dart' as _i9; import 'package:auto_route/auto_route.dart' as _i8;
import 'package:flutter/cupertino.dart' as _i13; import 'package:flutter/material.dart' as _i11;
import 'package:flutter/material.dart' as _i12; import 'package:mnemo_cards/features/tests/test_page.dart' as _i7;
import 'package:mnemo_cards/features/tests/test_page.dart' as _i8; import 'package:mnemo_cards/main.dart' as _i5;
import 'package:mnemo_cards/main.dart' as _i6;
import 'package:mnemo_cards/pages/auth_page.dart' as _i1; import 'package:mnemo_cards/pages/auth_page.dart' as _i1;
import 'package:mnemo_cards/pages/explore_page.dart' as _i4; import 'package:mnemo_cards/pages/home_page.dart' as _i4;
import 'package:mnemo_cards/pages/home_page.dart' as _i5; import 'package:mnemo_cards/pages/profile_page.dart' as _i6;
import 'package:mnemo_cards/pages/profile_page.dart' as _i7;
import 'package:mnemo_cards/widgets/card_game/card_game_page.dart' as _i2; import 'package:mnemo_cards/widgets/card_game/card_game_page.dart' as _i2;
import 'package:mnemo_cards/widgets/card_pack/card_pack_page.dart' as _i3; import 'package:mnemo_cards/widgets/card_pack/card_pack_page.dart' as _i3;
import 'package:mnemo_cards_common/mnemo_cards_common.dart' as _i10; import 'package:mnemo_cards_common/mnemo_cards_common.dart' as _i9;
abstract class $AppRouter extends _i9.RootStackRouter { abstract class $AppRouter extends _i8.RootStackRouter {
$AppRouter({super.navigatorKey}); $AppRouter({super.navigatorKey});
@override @override
final Map<String, _i9.PageFactory> pagesMap = { final Map<String, _i8.PageFactory> pagesMap = {
AuthPage.name: (routeData) { AuthPage.name: (routeData) {
return _i9.AutoRoutePage<dynamic>( return _i8.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: _i1.AuthPage(), child: _i1.AuthPage(),
); );
}, },
CardGamePage.name: (routeData) { CardGamePage.name: (routeData) {
final args = routeData.argsAs<CardGamePageArgs>(); final args = routeData.argsAs<CardGamePageArgs>();
return _i9.AutoRoutePage<dynamic>( return _i8.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: _i2.CardGamePage( child: _i2.CardGamePage(
cards: args.cards, cards: args.cards,
@ -50,7 +48,7 @@ abstract class $AppRouter extends _i9.RootStackRouter {
}, },
CardPackPage.name: (routeData) { CardPackPage.name: (routeData) {
final args = routeData.argsAs<CardPackPageArgs>(); final args = routeData.argsAs<CardPackPageArgs>();
return _i9.AutoRoutePage<dynamic>( return _i8.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: _i3.CardPackPage( child: _i3.CardPackPage(
args.previewDto, args.previewDto,
@ -58,35 +56,29 @@ abstract class $AppRouter extends _i9.RootStackRouter {
), ),
); );
}, },
ExplorePage.name: (routeData) {
return _i9.AutoRoutePage<dynamic>(
routeData: routeData,
child: _i4.ExplorePage(),
);
},
HomePage.name: (routeData) { HomePage.name: (routeData) {
return _i9.AutoRoutePage<dynamic>( return _i8.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: _i5.HomePage(), child: _i4.HomePage(),
); );
}, },
MainTabsPage.name: (routeData) { MainTabsPage.name: (routeData) {
return _i9.AutoRoutePage<dynamic>( return _i8.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const _i6.MainTabsPage(), child: const _i5.MainTabsPage(),
); );
}, },
ProfilePage.name: (routeData) { ProfilePage.name: (routeData) {
return _i9.AutoRoutePage<dynamic>( return _i8.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: _i7.ProfilePage(), child: _i6.ProfilePage(),
); );
}, },
TestPage.name: (routeData) { TestPage.name: (routeData) {
final args = routeData.argsAs<TestPageArgs>(); final args = routeData.argsAs<TestPageArgs>();
return _i9.AutoRoutePage<dynamic>( return _i8.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: _i8.TestPage(args.testId), child: _i7.TestPage(args.testId),
); );
}, },
}; };
@ -94,8 +86,8 @@ abstract class $AppRouter extends _i9.RootStackRouter {
/// generated route for /// generated route for
/// [_i1.AuthPage] /// [_i1.AuthPage]
class AuthPage extends _i9.PageRouteInfo<void> { class AuthPage extends _i8.PageRouteInfo<void> {
const AuthPage({List<_i9.PageRouteInfo>? children}) const AuthPage({List<_i8.PageRouteInfo>? children})
: super( : super(
AuthPage.name, AuthPage.name,
initialChildren: children, initialChildren: children,
@ -103,20 +95,20 @@ class AuthPage extends _i9.PageRouteInfo<void> {
static const String name = 'AuthPage'; static const String name = 'AuthPage';
static const _i9.PageInfo<void> page = _i9.PageInfo<void>(name); static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [_i2.CardGamePage] /// [_i2.CardGamePage]
class CardGamePage extends _i9.PageRouteInfo<CardGamePageArgs> { class CardGamePage extends _i8.PageRouteInfo<CardGamePageArgs> {
CardGamePage({ CardGamePage({
required List<_i10.GameCardDto> cards, required List<_i9.GameCardDto> cards,
required _i11.Color? color, required _i10.Color? color,
int? startCardId, int? startCardId,
bool infinite = false, bool infinite = false,
bool shuffle = true, bool shuffle = true,
_i12.Key? key, _i11.Key? key,
List<_i9.PageRouteInfo>? children, List<_i8.PageRouteInfo>? children,
}) : super( }) : super(
CardGamePage.name, CardGamePage.name,
args: CardGamePageArgs( args: CardGamePageArgs(
@ -132,8 +124,8 @@ class CardGamePage extends _i9.PageRouteInfo<CardGamePageArgs> {
static const String name = 'CardGamePage'; static const String name = 'CardGamePage';
static const _i9.PageInfo<CardGamePageArgs> page = static const _i8.PageInfo<CardGamePageArgs> page =
_i9.PageInfo<CardGamePageArgs>(name); _i8.PageInfo<CardGamePageArgs>(name);
} }
class CardGamePageArgs { class CardGamePageArgs {
@ -146,9 +138,9 @@ class CardGamePageArgs {
this.key, this.key,
}); });
final List<_i10.GameCardDto> cards; final List<_i9.GameCardDto> cards;
final _i11.Color? color; final _i10.Color? color;
final int? startCardId; final int? startCardId;
@ -156,7 +148,7 @@ class CardGamePageArgs {
final bool shuffle; final bool shuffle;
final _i12.Key? key; final _i11.Key? key;
@override @override
String toString() { String toString() {
@ -166,11 +158,11 @@ class CardGamePageArgs {
/// generated route for /// generated route for
/// [_i3.CardPackPage] /// [_i3.CardPackPage]
class CardPackPage extends _i9.PageRouteInfo<CardPackPageArgs> { class CardPackPage extends _i8.PageRouteInfo<CardPackPageArgs> {
CardPackPage({ CardPackPage({
required _i10.CardPackPreviewDto previewDto, required _i9.CardPackPreviewDto previewDto,
_i13.Key? key, _i11.Key? key,
List<_i9.PageRouteInfo>? children, List<_i8.PageRouteInfo>? children,
}) : super( }) : super(
CardPackPage.name, CardPackPage.name,
args: CardPackPageArgs( args: CardPackPageArgs(
@ -182,8 +174,8 @@ class CardPackPage extends _i9.PageRouteInfo<CardPackPageArgs> {
static const String name = 'CardPackPage'; static const String name = 'CardPackPage';
static const _i9.PageInfo<CardPackPageArgs> page = static const _i8.PageInfo<CardPackPageArgs> page =
_i9.PageInfo<CardPackPageArgs>(name); _i8.PageInfo<CardPackPageArgs>(name);
} }
class CardPackPageArgs { class CardPackPageArgs {
@ -192,9 +184,9 @@ class CardPackPageArgs {
this.key, this.key,
}); });
final _i10.CardPackPreviewDto previewDto; final _i9.CardPackPreviewDto previewDto;
final _i13.Key? key; final _i11.Key? key;
@override @override
String toString() { String toString() {
@ -203,23 +195,9 @@ class CardPackPageArgs {
} }
/// generated route for /// generated route for
/// [_i4.ExplorePage] /// [_i4.HomePage]
class ExplorePage extends _i9.PageRouteInfo<void> { class HomePage extends _i8.PageRouteInfo<void> {
const ExplorePage({List<_i9.PageRouteInfo>? children}) const HomePage({List<_i8.PageRouteInfo>? children})
: super(
ExplorePage.name,
initialChildren: children,
);
static const String name = 'ExplorePage';
static const _i9.PageInfo<void> page = _i9.PageInfo<void>(name);
}
/// generated route for
/// [_i5.HomePage]
class HomePage extends _i9.PageRouteInfo<void> {
const HomePage({List<_i9.PageRouteInfo>? children})
: super( : super(
HomePage.name, HomePage.name,
initialChildren: children, initialChildren: children,
@ -227,13 +205,13 @@ class HomePage extends _i9.PageRouteInfo<void> {
static const String name = 'HomePage'; static const String name = 'HomePage';
static const _i9.PageInfo<void> page = _i9.PageInfo<void>(name); static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [_i6.MainTabsPage] /// [_i5.MainTabsPage]
class MainTabsPage extends _i9.PageRouteInfo<void> { class MainTabsPage extends _i8.PageRouteInfo<void> {
const MainTabsPage({List<_i9.PageRouteInfo>? children}) const MainTabsPage({List<_i8.PageRouteInfo>? children})
: super( : super(
MainTabsPage.name, MainTabsPage.name,
initialChildren: children, initialChildren: children,
@ -241,13 +219,13 @@ class MainTabsPage extends _i9.PageRouteInfo<void> {
static const String name = 'MainTabsPage'; static const String name = 'MainTabsPage';
static const _i9.PageInfo<void> page = _i9.PageInfo<void>(name); static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [_i7.ProfilePage] /// [_i6.ProfilePage]
class ProfilePage extends _i9.PageRouteInfo<void> { class ProfilePage extends _i8.PageRouteInfo<void> {
const ProfilePage({List<_i9.PageRouteInfo>? children}) const ProfilePage({List<_i8.PageRouteInfo>? children})
: super( : super(
ProfilePage.name, ProfilePage.name,
initialChildren: children, initialChildren: children,
@ -255,15 +233,15 @@ class ProfilePage extends _i9.PageRouteInfo<void> {
static const String name = 'ProfilePage'; static const String name = 'ProfilePage';
static const _i9.PageInfo<void> page = _i9.PageInfo<void>(name); static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [_i8.TestPage] /// [_i7.TestPage]
class TestPage extends _i9.PageRouteInfo<TestPageArgs> { class TestPage extends _i8.PageRouteInfo<TestPageArgs> {
TestPage({ TestPage({
required String testId, required String testId,
List<_i9.PageRouteInfo>? children, List<_i8.PageRouteInfo>? children,
}) : super( }) : super(
TestPage.name, TestPage.name,
args: TestPageArgs(testId: testId), args: TestPageArgs(testId: testId),
@ -272,8 +250,8 @@ class TestPage extends _i9.PageRouteInfo<TestPageArgs> {
static const String name = 'TestPage'; static const String name = 'TestPage';
static const _i9.PageInfo<TestPageArgs> page = static const _i8.PageInfo<TestPageArgs> page =
_i9.PageInfo<TestPageArgs>(name); _i8.PageInfo<TestPageArgs>(name);
} }
class TestPageArgs { class TestPageArgs {

View file

@ -1,6 +1,7 @@
import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:mnemo_cards/managers/user_manager.dart'; import 'package:mnemo_cards/managers/user_manager.dart';
import 'package:mnemo_cards/utils/iterable_helper.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart';
class Analytics { class Analytics {
@ -148,22 +149,18 @@ class Analytics {
}.asFirebaseMap); }.asFirebaseMap);
} }
static crashlyticsError(Object? e, StackTrace? s) => static crashlyticsError({
_crashlytics.recordError(e, s); Object? e,
StackTrace? s,
static error({Map<String, String?>? data, String? message}) { String? message,
try { Map<String, Object?>? data,
_analytics.logEvent( }) =>
name: 'error', _crashlytics.recordError(
parameters: { e,
'msg': message, s,
if (data != null) ...data, reason: message,
}.asFirebaseMap, information: data?.asFirebaseMap.entries.toList() ?? [],
); );
} catch (e, s) {
_crashlytics.recordError(e, s);
}
}
} }
extension on Map { extension on Map {

View file

@ -7,12 +7,14 @@ import 'package:mnemo_cards_common/mnemo_cards_common.dart';
class ImagesHolder { class ImagesHolder {
Map<String, MemoryImage> _images = {}; Map<String, MemoryImage> _images = {};
Map<String, MemoryImage> _packImages = {}; Map<String, MemoryImage> _packImages = {};
String? packId;
DateTime? unlockTime; DateTime? unlockTime;
ImagesHolder(); ImagesHolder();
void setPackImages(Map<String, MemoryImage> images) { void setPackImages(Map<String, MemoryImage> images, String packId) {
_packImages = Map.from(images); _packImages = Map.from(images);
this.packId = packId;
} }
void setImage(String key, MemoryImage image) { void setImage(String key, MemoryImage image) {
@ -22,6 +24,7 @@ class ImagesHolder {
void clear() { void clear() {
_images.clear(); _images.clear();
_packImages.clear(); _packImages.clear();
packId = null;
} }
MemoryImage? get(String id) { MemoryImage? get(String id) {

View file

@ -10,6 +10,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../../di/locator.dart'; import '../../di/locator.dart';
import '../analytics/analytics.dart';
// persist // persist
class PackCacheManager { class PackCacheManager {
@ -21,6 +22,24 @@ class PackCacheManager {
_sharedPreferences = await SharedPreferences.getInstance(); _sharedPreferences = await SharedPreferences.getInstance();
} }
Future<void> markPackForUpdate(String packId) async {
try {
final dir = await _packDirectory(packId);
final file = File('${dir.path}/pack.json');
if (file.existsSync()) {
final json = jsonEncode(await file.readAsString()) as Map;
json['id'] = '0';
await file.writeAsString(jsonEncode(json));
}
} catch (e, s) {
Analytics.crashlyticsError(
e: e,
s: s,
message: 'Cant mark pack for update',
);
}
}
Future<CardPackDto> loadPackFromCache( Future<CardPackDto> loadPackFromCache(
String packId, { String packId, {
bool withCardImages = true, bool withCardImages = true,
@ -41,23 +60,25 @@ class PackCacheManager {
log('Cards ${packId}: ${pack.cards.length}'); log('Cards ${packId}: ${pack.cards.length}');
if (pack.cards.isNotEmpty) { if (pack.cards.isNotEmpty) {
log('Loading cards ${packId}'); log('Loading cards ${packId}');
for (final dto in pack.cards) { await Future.wait(
final file = File('${packDir.path}/images/${dto.id}'); pack.cards.map((dto) async {
if (file.existsSync()) { final file = File('${packDir.path}/images/${dto.id}');
try { if (await file.exists()) {
images[dto.id.toString()] = try {
MemoryImage(await file.readAsBytes()); images[dto.id.toString()] =
} catch (e) { MemoryImage(await file.readAsBytes());
throw Exception('Cant load pack $packId $e'); } catch (e) {
markPackForUpdate(packId);
throw Exception('Cant read file $packId $e');
}
} else {
log('Cant load card ${dto.id} from $packId, skiping');
} }
} else { }),
log('Cant load card ${dto.id} from $packId, skiping'); );
continue;
}
}
} }
log('Pack loaded ${packId}'); log('Pack loaded ${packId}');
locator.imagesHolder.setPackImages(images); locator.imagesHolder.setPackImages(images, packId);
} }
return pack; return pack;
} catch (e, s) { } catch (e, s) {

View file

@ -61,9 +61,6 @@ class PackManager {
yield await _cacheManager.loadPackFromCache(id); yield await _cacheManager.loadPackFromCache(id);
} on Object catch (e, s) { } on Object catch (e, s) {
log('Cant fetch pack', error: e, stackTrace: s); log('Cant fetch pack', error: e, stackTrace: s);
} finally {
log('Updating');
unawaited(locator.packUpdater.updateAvailablePacks());
} }
} }
} else { } else {
@ -78,9 +75,6 @@ class PackManager {
yield await _cacheManager.loadPackFromCache(id); yield await _cacheManager.loadPackFromCache(id);
} on Object catch (e, s) { } on Object catch (e, s) {
log('Cant fetch pack', error: e, stackTrace: s); log('Cant fetch pack', error: e, stackTrace: s);
} finally {
log('Updating');
unawaited(locator.packUpdater.updateAvailablePacks());
} }
} }
} }
@ -93,7 +87,6 @@ class PackManager {
Future<void> clearCache() async { Future<void> clearCache() async {
_packHolder.clear(); _packHolder.clear();
await _cacheManager.clearCache(); await _cacheManager.clearCache();
await globalSharedPreferences.clear();
} }
Stream<List<CardPackDto>?> get packsStream => Stream<List<CardPackDto>?> get packsStream =>

View file

@ -30,7 +30,7 @@ class PackUpdater {
Future<void> init() async { Future<void> init() async {
final pollStream = Stream.periodic(const Duration(seconds: 60)); final pollStream = Stream.periodic(const Duration(seconds: 60));
pollStream.startWith(-1).listen((event) async { pollStream.listen((event) async {
await updateAvailablePacks(); await updateAvailablePacks();
try { try {
final r = await _packsApi.getPacksPreviews(null); final r = await _packsApi.getPacksPreviews(null);
@ -105,18 +105,6 @@ class PackUpdater {
} }
} }
Future<bool> updatePackInfoWithCardsAndSave(String packId) async {
try {
// todo
// final pack = await _updatePackInfoWithCards(packId);
// await _cacheManager.savePack(pack, updateCards: true);
return true;
} catch (e) {
log(e.toString());
}
return false;
}
Future<CardPackDto> updatePackAndSaveImages(String packId) async { Future<CardPackDto> updatePackAndSaveImages(String packId) async {
try { try {
log('Updating with cards ${packId}', name: 'updatePackWithCards'); log('Updating with cards ${packId}', name: 'updatePackWithCards');

View file

@ -39,6 +39,7 @@ enum Result {
correct, correct,
wrong, wrong,
skiped, skiped,
not_visited,
} }
class _ProgressWidgetState extends State<ProgressWidget> { class _ProgressWidgetState extends State<ProgressWidget> {
@ -99,7 +100,8 @@ class _ProgressWidgetState extends State<ProgressWidget> {
widthFactor: length == 0 widthFactor: length == 0
? 0 ? 0
: (results : (results
.where((r) => r != Result.skiped) .where((r) =>
r != Result.not_visited)
.length) / .length) /
length, length,
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),

View file

@ -14,6 +14,7 @@ class InputButtonsTestState extends TestQuestionState {
final List<String> answerIds; final List<String> answerIds;
final bool isCorrect; final bool isCorrect;
final bool isAnswered; final bool isAnswered;
final bool isSkipped;
final int seed; final int seed;
InputButtonsTestState({ InputButtonsTestState({
@ -22,6 +23,7 @@ class InputButtonsTestState extends TestQuestionState {
this.answerIds = const [], this.answerIds = const [],
this.isCorrect = false, this.isCorrect = false,
this.isAnswered = false, this.isAnswered = false,
this.isSkipped = false,
super.testType = TestQuestionType.simple, super.testType = TestQuestionType.simple,
}) {} });
} }

View file

@ -17,6 +17,8 @@ abstract class _$InputButtonsTestStateCWProxy {
InputButtonsTestState isAnswered(bool isAnswered); InputButtonsTestState isAnswered(bool isAnswered);
InputButtonsTestState isSkipped(bool isSkipped);
InputButtonsTestState testType(TestQuestionType testType); InputButtonsTestState testType(TestQuestionType testType);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `InputButtonsTestState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `InputButtonsTestState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
@ -31,6 +33,7 @@ abstract class _$InputButtonsTestStateCWProxy {
List<String>? answerIds, List<String>? answerIds,
bool? isCorrect, bool? isCorrect,
bool? isAnswered, bool? isAnswered,
bool? isSkipped,
TestQuestionType? testType, TestQuestionType? testType,
}); });
} }
@ -59,6 +62,9 @@ class _$InputButtonsTestStateCWProxyImpl
InputButtonsTestState isAnswered(bool isAnswered) => InputButtonsTestState isAnswered(bool isAnswered) =>
this(isAnswered: isAnswered); this(isAnswered: isAnswered);
@override
InputButtonsTestState isSkipped(bool isSkipped) => this(isSkipped: isSkipped);
@override @override
InputButtonsTestState testType(TestQuestionType testType) => InputButtonsTestState testType(TestQuestionType testType) =>
this(testType: testType); this(testType: testType);
@ -77,6 +83,7 @@ class _$InputButtonsTestStateCWProxyImpl
Object? answerIds = const $CopyWithPlaceholder(), Object? answerIds = const $CopyWithPlaceholder(),
Object? isCorrect = const $CopyWithPlaceholder(), Object? isCorrect = const $CopyWithPlaceholder(),
Object? isAnswered = const $CopyWithPlaceholder(), Object? isAnswered = const $CopyWithPlaceholder(),
Object? isSkipped = const $CopyWithPlaceholder(),
Object? testType = const $CopyWithPlaceholder(), Object? testType = const $CopyWithPlaceholder(),
}) { }) {
return InputButtonsTestState( return InputButtonsTestState(
@ -101,6 +108,10 @@ class _$InputButtonsTestStateCWProxyImpl
? _value.isAnswered ? _value.isAnswered
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
: isAnswered as bool, : isAnswered as bool,
isSkipped: isSkipped == const $CopyWithPlaceholder() || isSkipped == null
? _value.isSkipped
// ignore: cast_nullable_to_non_nullable
: isSkipped as bool,
testType: testType == const $CopyWithPlaceholder() || testType == null testType: testType == const $CopyWithPlaceholder() || testType == null
? _value.testType ? _value.testType
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
@ -134,6 +145,7 @@ InputButtonsTestState _$InputButtonsTestStateFromJson(
const [], const [],
isCorrect: json['isCorrect'] as bool? ?? false, isCorrect: json['isCorrect'] as bool? ?? false,
isAnswered: json['isAnswered'] as bool? ?? false, isAnswered: json['isAnswered'] as bool? ?? false,
isSkipped: json['isSkipped'] as bool? ?? false,
testType: testType:
$enumDecodeNullable(_$TestQuestionTypeEnumMap, json['testType']) ?? $enumDecodeNullable(_$TestQuestionTypeEnumMap, json['testType']) ??
TestQuestionType.simple, TestQuestionType.simple,
@ -147,6 +159,7 @@ Map<String, dynamic> _$InputButtonsTestStateToJson(
'answerIds': instance.answerIds, 'answerIds': instance.answerIds,
'isCorrect': instance.isCorrect, 'isCorrect': instance.isCorrect,
'isAnswered': instance.isAnswered, 'isAnswered': instance.isAnswered,
'isSkipped': instance.isSkipped,
'seed': instance.seed, 'seed': instance.seed,
}; };

View file

@ -13,6 +13,7 @@ class SimpleTestState extends TestQuestionState {
final String answer; final String answer;
final bool isCorrect; final bool isCorrect;
final bool isAnswered; final bool isAnswered;
final bool isSkipped;
final int seed; final int seed;
SimpleTestState({ SimpleTestState({
@ -20,6 +21,7 @@ class SimpleTestState extends TestQuestionState {
this.answer = '', this.answer = '',
this.isCorrect = false, this.isCorrect = false,
this.isAnswered = false, this.isAnswered = false,
this.isSkipped = false,
super.testType = TestQuestionType.simple, super.testType = TestQuestionType.simple,
}); });
} }

View file

@ -15,6 +15,8 @@ abstract class _$SimpleTestStateCWProxy {
SimpleTestState isAnswered(bool isAnswered); SimpleTestState isAnswered(bool isAnswered);
SimpleTestState isSkipped(bool isSkipped);
SimpleTestState testType(TestQuestionType testType); SimpleTestState testType(TestQuestionType testType);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `SimpleTestState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `SimpleTestState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
@ -28,6 +30,7 @@ abstract class _$SimpleTestStateCWProxy {
String? answer, String? answer,
bool? isCorrect, bool? isCorrect,
bool? isAnswered, bool? isAnswered,
bool? isSkipped,
TestQuestionType? testType, TestQuestionType? testType,
}); });
} }
@ -50,6 +53,9 @@ class _$SimpleTestStateCWProxyImpl implements _$SimpleTestStateCWProxy {
@override @override
SimpleTestState isAnswered(bool isAnswered) => this(isAnswered: isAnswered); SimpleTestState isAnswered(bool isAnswered) => this(isAnswered: isAnswered);
@override
SimpleTestState isSkipped(bool isSkipped) => this(isSkipped: isSkipped);
@override @override
SimpleTestState testType(TestQuestionType testType) => SimpleTestState testType(TestQuestionType testType) =>
this(testType: testType); this(testType: testType);
@ -67,6 +73,7 @@ class _$SimpleTestStateCWProxyImpl implements _$SimpleTestStateCWProxy {
Object? answer = const $CopyWithPlaceholder(), Object? answer = const $CopyWithPlaceholder(),
Object? isCorrect = const $CopyWithPlaceholder(), Object? isCorrect = const $CopyWithPlaceholder(),
Object? isAnswered = const $CopyWithPlaceholder(), Object? isAnswered = const $CopyWithPlaceholder(),
Object? isSkipped = const $CopyWithPlaceholder(),
Object? testType = const $CopyWithPlaceholder(), Object? testType = const $CopyWithPlaceholder(),
}) { }) {
return SimpleTestState( return SimpleTestState(
@ -87,6 +94,10 @@ class _$SimpleTestStateCWProxyImpl implements _$SimpleTestStateCWProxy {
? _value.isAnswered ? _value.isAnswered
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
: isAnswered as bool, : isAnswered as bool,
isSkipped: isSkipped == const $CopyWithPlaceholder() || isSkipped == null
? _value.isSkipped
// ignore: cast_nullable_to_non_nullable
: isSkipped as bool,
testType: testType == const $CopyWithPlaceholder() || testType == null testType: testType == const $CopyWithPlaceholder() || testType == null
? _value.testType ? _value.testType
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
@ -111,6 +122,7 @@ SimpleTestState _$SimpleTestStateFromJson(Map<String, dynamic> json) =>
answer: json['answer'] as String? ?? '', answer: json['answer'] as String? ?? '',
isCorrect: json['isCorrect'] as bool? ?? false, isCorrect: json['isCorrect'] as bool? ?? false,
isAnswered: json['isAnswered'] as bool? ?? false, isAnswered: json['isAnswered'] as bool? ?? false,
isSkipped: json['isSkipped'] as bool? ?? false,
testType: testType:
$enumDecodeNullable(_$TestQuestionTypeEnumMap, json['testType']) ?? $enumDecodeNullable(_$TestQuestionTypeEnumMap, json['testType']) ??
TestQuestionType.simple, TestQuestionType.simple,
@ -122,6 +134,7 @@ Map<String, dynamic> _$SimpleTestStateToJson(SimpleTestState instance) =>
'answer': instance.answer, 'answer': instance.answer,
'isCorrect': instance.isCorrect, 'isCorrect': instance.isCorrect,
'isAnswered': instance.isAnswered, 'isAnswered': instance.isAnswered,
'isSkipped': instance.isSkipped,
'seed': instance.seed, 'seed': instance.seed,
}; };

View file

@ -9,9 +9,12 @@ class TestQuestionState {
bool get isCorrect => false; bool get isCorrect => false;
bool get isAnswered => false; final bool isAnswered;
final bool isSkipped;
const TestQuestionState({ const TestQuestionState({
required this.testType, required this.testType,
this.isAnswered = false,
this.isSkipped = false,
}); });
} }

View file

@ -9,6 +9,10 @@ part of 'test_question_state.dart';
abstract class _$TestQuestionStateCWProxy { abstract class _$TestQuestionStateCWProxy {
TestQuestionState testType(TestQuestionType testType); TestQuestionState testType(TestQuestionType testType);
TestQuestionState isAnswered(bool isAnswered);
TestQuestionState isSkipped(bool isSkipped);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TestQuestionState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TestQuestionState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
/// ///
/// Usage /// Usage
@ -17,6 +21,8 @@ abstract class _$TestQuestionStateCWProxy {
/// ```` /// ````
TestQuestionState call({ TestQuestionState call({
TestQuestionType? testType, TestQuestionType? testType,
bool? isAnswered,
bool? isSkipped,
}); });
} }
@ -30,6 +36,12 @@ class _$TestQuestionStateCWProxyImpl implements _$TestQuestionStateCWProxy {
TestQuestionState testType(TestQuestionType testType) => TestQuestionState testType(TestQuestionType testType) =>
this(testType: testType); this(testType: testType);
@override
TestQuestionState isAnswered(bool isAnswered) => this(isAnswered: isAnswered);
@override
TestQuestionState isSkipped(bool isSkipped) => this(isSkipped: isSkipped);
@override @override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TestQuestionState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TestQuestionState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
@ -40,12 +52,23 @@ class _$TestQuestionStateCWProxyImpl implements _$TestQuestionStateCWProxy {
/// ```` /// ````
TestQuestionState call({ TestQuestionState call({
Object? testType = const $CopyWithPlaceholder(), Object? testType = const $CopyWithPlaceholder(),
Object? isAnswered = const $CopyWithPlaceholder(),
Object? isSkipped = const $CopyWithPlaceholder(),
}) { }) {
return TestQuestionState( return TestQuestionState(
testType: testType == const $CopyWithPlaceholder() || testType == null testType: testType == const $CopyWithPlaceholder() || testType == null
? _value.testType ? _value.testType
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
: testType as TestQuestionType, : testType as TestQuestionType,
isAnswered:
isAnswered == const $CopyWithPlaceholder() || isAnswered == null
? _value.isAnswered
// ignore: cast_nullable_to_non_nullable
: isAnswered as bool,
isSkipped: isSkipped == const $CopyWithPlaceholder() || isSkipped == null
? _value.isSkipped
// ignore: cast_nullable_to_non_nullable
: isSkipped as bool,
); );
} }
} }

View file

@ -0,0 +1,149 @@
import 'package:flutter/material.dart';
/// This [ScrollPhysics] allows you to block the movement in the horizontal axis.
/// You can block both right and left movements.
///
/// {@tool sample}
///
/// This sample shows a [HorizontalBlockedScrollPhysics] blocking left movement
///
/// ```dart
/// HorizontalBlockedScrollPhysics(blockLeftMovent: true, blockRightMovement: false);
/// ```
/// {@end-tool}
class HorizontalBlockedScrollPhysics extends ScrollPhysics {
/// If [true] it blocks the left movement.
final bool blockLeftMovement;
/// If [true] it blocks the right movement.
final bool blockRightMovement;
const HorizontalBlockedScrollPhysics({
ScrollPhysics? parent,
this.blockLeftMovement = false,
this.blockRightMovement = false,
}) : super(parent: parent);
@override
HorizontalBlockedScrollPhysics applyTo(ScrollPhysics? ancestor) {
return HorizontalBlockedScrollPhysics(
parent: buildParent(ancestor),
blockLeftMovement: blockLeftMovement,
blockRightMovement: blockRightMovement,
);
}
@override
double applyBoundaryConditions(ScrollMetrics position, double value) {
assert(() {
if (value == position.pixels) {
throw FlutterError(
'$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
}
return true;
}());
if (value < position.pixels &&
position.pixels <= position.minScrollExtent) {
return value - position.pixels;
}
if (position.maxScrollExtent <= position.pixels &&
position.pixels < value) {
// overscroll
return value - position.pixels;
}
if (value < position.minScrollExtent &&
position.minScrollExtent < position.pixels) {
// hit top edge
return value - position.minScrollExtent;
}
if (position.pixels < position.maxScrollExtent &&
position.maxScrollExtent < value) {
// hit bottom edge
return value - position.maxScrollExtent;
}
// If true, movement goes to the left. If it's a swipe, it goes to the left.
var isMovingLeft = value > position.pixels;
var screenIndex = (value / position.viewportDimension).floor();
var pointInScreen = value - (screenIndex * position.viewportDimension);
// If true, the middle point of the screen is in the left side of the screen.
// This will be useful in order to not block some movements when in returning position.
var isPointInScreenLeftRange =
pointInScreen < (position.viewportDimension / 2);
var delta = value - position.pixels;
// We're moving left and we want to block.
if (isMovingLeft && blockLeftMovement && isPointInScreenLeftRange) {
if (pointInScreen.abs() < delta.abs()) {
// fix for strong movements
return pointInScreen;
}
return delta;
}
// We're moving right and we want to block.
if (!isMovingLeft && blockRightMovement && !isPointInScreenLeftRange) {
return delta;
}
return super.applyBoundaryConditions(position, value);
}
}
/// This [ScrollPhysics] blocks the left movement in the horizontal axis allowing only movements to the right.
///
/// {@tool sample}
///
/// This sample shows a [LeftBlockedScrollPhysics] blocking left movement
///
/// ```dart
/// LeftBlockedScrollPhysics();
/// ```
/// {@end-tool}
class LeftBlockedScrollPhysics extends HorizontalBlockedScrollPhysics {
const LeftBlockedScrollPhysics({
ScrollPhysics? parent,
}) : super(parent: parent, blockLeftMovement: true);
@override
LeftBlockedScrollPhysics applyTo(ScrollPhysics? ancestor) {
return LeftBlockedScrollPhysics(
parent: buildParent(ancestor),
);
}
}
// This [ScrollPhysics] blocks the right movement in the horizontal axis allowing only movements to the left.
///
/// {@tool sample}
///
/// This sample shows a [RightBlockedScrollPhysics] blocking right movement
///
/// ```dart
/// RightBlockedScrollPhysics();
/// ```
/// {@end-tool}
class RightBlockedScrollPhysics extends HorizontalBlockedScrollPhysics {
const RightBlockedScrollPhysics({
ScrollPhysics? parent,
}) : super(parent: parent, blockRightMovement: true);
@override
RightBlockedScrollPhysics applyTo(ScrollPhysics? ancestor) {
return RightBlockedScrollPhysics(
parent: buildParent(ancestor),
);
}
}

View file

@ -30,6 +30,11 @@ class TestManager extends ChangeNotifier {
return tests; return tests;
} }
void skip(int id) {
testHolder.skip(id);
notifyListeners();
}
Stream<List<TestDto>> loadTestsStream({String? packId}) { Stream<List<TestDto>> loadTestsStream({String? packId}) {
return Stream.periodic( return Stream.periodic(
Duration(seconds: 60), Duration(seconds: 60),

View file

@ -1,11 +1,15 @@
import 'dart:developer';
import 'dart:math' as math;
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:mnemo_cards/features/tests/question_states/test_question_state.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import 'package:pie_chart/pie_chart.dart'; import 'package:pie_chart/pie_chart.dart';
import '../../di/locator.dart'; import '../../di/locator.dart';
import 'scroll_physics.dart';
import 'test_progress_widget.dart'; import 'test_progress_widget.dart';
import 'test_widgets/input_buttons_test.dart'; import 'test_widgets/input_buttons_test.dart';
import 'test_widgets/simple_test.dart'; import 'test_widgets/simple_test.dart';
@ -21,13 +25,39 @@ class TestPage extends StatefulWidget {
} }
class _TestPageState extends State<TestPage> { class _TestPageState extends State<TestPage> {
PageController get pageController => locator.testManager.pageController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
locator.testManager.setActiveTest(widget.testId); locator.testManager.setActiveTest(widget.testId);
locator.testManager.preloadImages(context); locator.testManager.preloadImages(context);
pageController.addListener(_pageScrollListener);
} }
@override
void dispose() {
pageController.removeListener(_pageScrollListener);
super.dispose();
}
void _pageScrollListener() {
final index = pageController.page?.floor() ?? 0;
if (index > 0) {
final prevId =
locator.testManager.questions[index - 1].id!;
final state =
locator.testManager.getState(prevId);
if (state?.isAnswered == false) {
log('Question skipped');
locator.testManager.skip(prevId);
}
}
}
int lastTestIndex = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -60,16 +90,11 @@ class _TestPageState extends State<TestPage> {
), ),
Expanded( Expanded(
child: PageView.builder( child: PageView.builder(
physics: NeverScrollableScrollPhysics(), //ClampingScrollPhysics(), physics: RightBlockedScrollPhysics(),
controller: locator.testManager.pageController, controller: locator.testManager.pageController,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index > 0) { if (index > locator.testManager.questions.length) {
final state = locator.testManager.getState( return null;
locator.testManager.questions[index - 1].id!,
);
if (state?.isCorrect != true) {
return null;
}
} }
if (index == locator.testManager.questions.length) { if (index == locator.testManager.questions.length) {
return Column( return Column(
@ -152,6 +177,7 @@ class _TestPageState extends State<TestPage> {
); );
} }
lastTestIndex = math.max(index, lastTestIndex);
return switch (locator.testManager return switch (locator.testManager
.getTest(index) .getTest(index)
.questionType) { .questionType) {

View file

@ -35,10 +35,12 @@ class _TestProgressWidgetState extends State<TestProgressWidget> {
results.add(Result.correct); results.add(Result.correct);
} else { } else {
// results.add(Result.wrong); // results.add(Result.wrong);
results.add(Result.skiped); results.add(Result.not_visited);
} }
} else if (state.isSkipped) {
results.add(Result.correct);
} else { } else {
results.add(Result.skiped); results.add(Result.not_visited);
} }
} }
if (mounted) { if (mounted) {

View file

@ -15,6 +15,31 @@ class TestHolder {
TestHolder() : __state = _TestState({}); TestHolder() : __state = _TestState({});
void skip(int id) {
final state = testQuestionState(id);
switch (state?.testType) {
case TestQuestionType.simple:
state as SimpleTestState;
setQuestionState(id, state.copyWith(isSkipped: true));
break;
case TestQuestionType.input_buttons:
state as InputButtonsTestState;
setQuestionState(id, state.copyWith(isSkipped: true));
break;
case TestQuestionType.matrix:
// TODO: Handle this case.
break;
case TestQuestionType.match:
// TODO: Handle this case.
break;
case TestQuestionType.undefined:
// TODO: Handle this case.
case null:
// TODO: Handle this case.
break;
}
}
void setQuestionState(int id, TestQuestionState state) => _state = _TestState( void setQuestionState(int id, TestQuestionState state) => _state = _TestState(
Map.from(_state.questions..[id] = state), Map.from(_state.questions..[id] = state),
); );

View file

@ -1,12 +1,7 @@
import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mnemo_cards/features/tests/test_manager.dart'; import 'package:mnemo_cards/features/tests/test_manager.dart';
import 'package:mnemo_cards/features/tests/test_widgets/common_test_question_widget.dart'; import 'package:mnemo_cards/features/tests/test_widgets/common_test_question_widget.dart';
import 'package:mnemo_cards/features/tests/test_widgets/test_image.dart';
import 'package:mnemo_cards/theme/themes.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import '../../../di/locator.dart'; import '../../../di/locator.dart';
import '../question_states/input_buttons_test_state.dart'; import '../question_states/input_buttons_test_state.dart';
@ -39,8 +34,6 @@ class InputButtonsTestWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context).textTheme;
// final random = Random(state.seed);
final buttons = model.buttons; //..shuffle(random); final buttons = model.buttons; //..shuffle(random);
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
@ -49,6 +42,7 @@ class InputButtonsTestWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
flex: 2,
child: CommonTestQuestionWidget( child: CommonTestQuestionWidget(
text: model.text, text: model.text,
image: model.image, image: model.image,
@ -142,6 +136,7 @@ class InputButtonsTestWidget extends StatelessWidget {
]; ];
return Expanded( return Expanded(
flex: 3,
child: Column( child: Column(
children: [ children: [
Expanded( Expanded(

View file

@ -59,7 +59,6 @@ class UserManager {
ExternalIdType idType, ExternalIdType idType,
String? name, String? name,
) async { ) async {
await locator.packCacheManager.clearCache();
final (user, token) = final (user, token) =
await _repository.createOrGetUser(externalId, idType, name); await _repository.createOrGetUser(externalId, idType, name);
_setAuthToken(token); _setAuthToken(token);
@ -75,14 +74,14 @@ class UserManager {
await _sharedPreferences!.remove('authToken'); await _sharedPreferences!.remove('authToken');
} }
Future<void> _updateUser() async { Future<UserDto?> _updateUser() async {
String? authToken = _sharedPreferences!.getString('authToken'); String? authToken = _sharedPreferences!.getString('authToken');
if (authToken != null) { if (authToken != null) {
_setAuthToken(authToken); _setAuthToken(authToken);
try { try {
final fetchedUser = await _repository.getUser(); final fetchedUser = await _repository.getUser();
userStateHolder.setUser(fetchedUser!); userStateHolder.setUser(fetchedUser!);
return; return fetchedUser;
} catch (e) { } catch (e) {
_clearAuthToken(); _clearAuthToken();
AppRouter.openAuthOrProfile(); AppRouter.openAuthOrProfile();
@ -95,12 +94,11 @@ class UserManager {
// final id = deviceInfo.id; // final id = deviceInfo.id;
// await _createUser(id, ExternalIdType.device); // await _createUser(id, ExternalIdType.device);
// } // }
return null;
} }
Future<void> init() async { Future<void> init() async {
_sharedPreferences = await SharedPreferences.getInstance(); _sharedPreferences = await SharedPreferences.getInstance();
//todo add ios
try { try {
await _updateUser(); await _updateUser();
} on Object catch (e, s) { } on Object catch (e, s) {
@ -120,12 +118,15 @@ class UserManager {
await _createUser(token!, ExternalIdType.google, name); await _createUser(token!, ExternalIdType.google, name);
} }
})) }))
..add(userStateHolder.asNullableStream.listen((user) async { ..add(userStateHolder.asNullableStream
.distinct((a, b) => a?.id == b?.id)
.listen((user) async {
try { try {
if (user == null) { if (user == null) {
await _updateUser(); AppRouter.openAuthOrProfile();
} else { } else {
try { try {
AppRouter.closeAuthPage();
await locator.previewPackPoller.poll(); await locator.previewPackPoller.poll();
await locator.packManager.clearCache(); await locator.packManager.clearCache();
await locator.packUpdater.updateAvailablePacks(); await locator.packUpdater.updateAvailablePacks();
@ -144,19 +145,7 @@ class UserManager {
} }
} }
} }
})) }));
..add(
userStateHolder.asNullableStream
.map((user) => user != null)
.distinct()
.listen((hasUser) {
if (hasUser) {
appRouter.replace(PageRouteInfo(HomePage.name));
} else {
AppRouter.openAuthOrProfile();
}
}),
);
} }
Future<void> dispose() async { Future<void> dispose() async {

View file

@ -1,48 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mnemo_cards/di/locator.dart';
import '../widgets/packs_menu_widget.dart';
@RoutePage()
class ExplorePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context).textTheme;
return Column(
children: [
Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(12.0),
child: Text(
'Blah - blah',
style: theme.headlineSmall,
),
),
Expanded(
child: PacksMenuWidget(
locator.repository,
locator.packManager,
),
),
TextButton(
onPressed: () async {
await locator.packManager.clearCache();
locator.testManager.clearStates();
await locator.packUpdater.updateAvailablePacks();
},
child: Text('Clear cache'),
),
TextButton(
onPressed: () async {
locator.userManager.login();
},
child: Text('Login'),
),
],
);
}
const ExplorePage();
}

View file

@ -36,9 +36,6 @@ class AvailablePack extends StatelessWidget {
CardPackHeader.fromDto(cardPack), CardPackHeader.fromDto(cardPack),
Expanded( Expanded(
child: ListView( child: ListView(
physics: BouncingScrollPhysics(
decelerationRate: ScrollDecelerationRate.fast,
parent: AlwaysScrollableScrollPhysics()),
controller: _mainScrollController, controller: _mainScrollController,
children: [ children: [
StatefulBuilder(builder: (context, setState) { StatefulBuilder(builder: (context, setState) {

View file

@ -34,17 +34,17 @@ class _CardPackPage extends State<CardPackPage> {
), ),
initialData: null, initialData: null,
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) return SizedBox.shrink(); if (!snapshot.hasData) return Loading(widget.previewDto);
if (snapshot.requireData.responseType == if (snapshot.requireData.responseType ==
GetCardPackResponseType.loading) GetCardPackResponseType.loading)
return Loading(widget.previewDto); return Loading(widget.previewDto);
final response = snapshot.requireData; final response = snapshot.requireData;
if (response is CardPackBuyDto) if (response is CardPackBuyDto) {
return BuyPack(response); return BuyPack(response);
else if (response is CardPackDto) { } else if (response is CardPackDto) {
return AvailablePack(response); return AvailablePack(response);
} }
Analytics.error( Analytics.crashlyticsError(
data: {'response': jsonEncode(snapshot.data?.toJson() ?? '{}')}, data: {'response': jsonEncode(snapshot.data?.toJson() ?? '{}')},
message: 'cant load pack ${widget.previewDto.id}', message: 'cant load pack ${widget.previewDto.id}',
); );
@ -54,7 +54,9 @@ class _CardPackPage extends State<CardPackPage> {
CardPackHeader.fromDto(null), CardPackHeader.fromDto(null),
Expanded( Expanded(
child: Center( child: Center(
child: Text('Error'), child: Text(
'Ошибка, попробуйте еще раз или попробуйте обновить приложение',
),
), ),
), ),
], ],

View file

@ -201,7 +201,8 @@ class FrontCard extends StatelessWidget {
ar = 16 / 9; ar = 16 / 9;
} }
final textScale = min(1.0, ar / targetTextAr) * alpha * alpha; final textScale = min(1.0, ar / targetTextAr) * alpha * alpha;
final imageScale = 0.9 * min(1.0, pow(ar / targetImageAr, 1.5 * alpha)); final imageScale =
0.9 * min(1.0, pow(ar / targetImageAr, 1.5 * alpha));
return Material( return Material(
borderOnForeground: false, borderOnForeground: false,
type: MaterialType.transparency, type: MaterialType.transparency,
@ -310,9 +311,11 @@ class FrontCard extends StatelessWidget {
syncDuration: Duration.zero, syncDuration: Duration.zero,
), ),
), ),
if (card.mnemo?.isNotEmpty == true)
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
if (card.mnemo?.trim().isNotEmpty != true) {
return;
}
final sp = await SharedPreferences.getInstance(); final sp = await SharedPreferences.getInstance();
if (sp.getBool('sound_on') != false) { if (sp.getBool('sound_on') != false) {
FlutterTts flutterTts = FlutterTts(); FlutterTts flutterTts = FlutterTts();
@ -323,7 +326,7 @@ class FrontCard extends StatelessWidget {
} }
}, },
child: MnemoText( child: MnemoText(
card.mnemo, card.mnemo?.isNotEmpty == true ? card.mnemo : ' ',
textStyle: TextStyle( textStyle: TextStyle(
fontSize: 20 * textScale, fontSize: 20 * textScale,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,

View file

@ -983,6 +983,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.1.0"
reorderables:
dependency: "direct main"
description:
name: reorderables
sha256: "004a886e4878df1ee27321831c838bc1c976311f4ca6a74ce7d561e506540a77"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
retrofit: retrofit:
dependency: transitive dependency: transitive
description: description:

View file

@ -61,6 +61,7 @@ dependencies:
firebase_analytics: ^10.10.7 firebase_analytics: ^10.10.7
in_app_purchase: ^3.2.0 in_app_purchase: ^3.2.0
dot_navigation_bar: ^1.0.2 dot_navigation_bar: ^1.0.2
reorderables: ^0.6.0
story: ^1.1.0 story: ^1.1.0
flutter_screenutil: ^5.9.0 flutter_screenutil: ^5.9.0
pie_chart: ^5.4.0 pie_chart: ^5.4.0