app
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 12 KiB |
BIN
images/icon.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 540 KiB After Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 890 B After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 9.8 KiB |
|
|
@ -44,7 +44,7 @@ class _AllCardsState extends State<AllCards> {
|
||||||
_cache.clear();
|
_cache.clear();
|
||||||
while (loadingIds.isNotEmpty) {
|
while (loadingIds.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
final ids = loadingIds.take(20).toList();
|
final ids = loadingIds.take(10).toList();
|
||||||
loadingIds.removeRange(0, ids.length);
|
loadingIds.removeRange(0, ids.length);
|
||||||
final cardsPage = await api.getCards(ids);
|
final cardsPage = await api.getCards(ids);
|
||||||
for (final card in cardsPage) {
|
for (final card in cardsPage) {
|
||||||
|
|
|
||||||
|
|
@ -24,31 +24,45 @@ import '../managers/user_manager.dart';
|
||||||
GetIt getIt = GetIt.I;
|
GetIt getIt = GetIt.I;
|
||||||
|
|
||||||
Future<void> setInjections() async {
|
Future<void> setInjections() async {
|
||||||
getIt.registerSingletonAsync<DioProvider>(
|
final _dio = await DioProvider().init();
|
||||||
() => DioProvider().init(),
|
getIt.registerSingleton<DioProvider>(_dio);
|
||||||
);
|
print('set dio');
|
||||||
|
|
||||||
getIt.registerLazySingleton<HttpRepository>(
|
getIt.registerLazySingleton<HttpRepository>(
|
||||||
() => HttpRepository(getIt.get<DioProvider>().dio),
|
() => HttpRepository(getIt.get<DioProvider>().dio),
|
||||||
);
|
);
|
||||||
|
print('set repo');
|
||||||
|
|
||||||
getIt.registerLazySingleton<PacksApi>(
|
getIt.registerLazySingleton<PacksApi>(
|
||||||
() => PacksApi(getIt.get<DioProvider>().dio),
|
() => PacksApi(getIt.get<DioProvider>().dio),
|
||||||
);
|
);
|
||||||
if (ADMIN_BUILD)
|
print('set pack');
|
||||||
|
|
||||||
|
if (ADMIN_BUILD) {
|
||||||
getIt.registerLazySingleton<AdminApi>(
|
getIt.registerLazySingleton<AdminApi>(
|
||||||
() => AdminApi(getIt.get<DioProvider>().dio),
|
() => AdminApi(getIt.get<DioProvider>().dio));
|
||||||
);
|
print('set admin');
|
||||||
|
}
|
||||||
|
|
||||||
getIt.registerLazySingleton<PackHolder>(
|
getIt.registerLazySingleton<PackHolder>(
|
||||||
() => PackHolder(),
|
() => PackHolder(),
|
||||||
);
|
);
|
||||||
|
print('set pack holder');
|
||||||
|
|
||||||
getIt.registerLazySingleton<PreviewPackHolder>(
|
getIt.registerLazySingleton<PreviewPackHolder>(
|
||||||
() => PreviewPackHolder(),
|
() => PreviewPackHolder(),
|
||||||
);
|
);
|
||||||
|
print('set preview');
|
||||||
|
|
||||||
getIt.registerLazySingleton<PackCacheManager>(
|
getIt.registerLazySingleton<PackCacheManager>(
|
||||||
() => PackCacheManager(),
|
() => PackCacheManager(),
|
||||||
);
|
);
|
||||||
|
print('set cache');
|
||||||
|
|
||||||
getIt.registerLazySingleton<ImagesHolder>(
|
getIt.registerLazySingleton<ImagesHolder>(
|
||||||
() => ImagesHolder(),
|
() => ImagesHolder(),
|
||||||
);
|
);
|
||||||
|
print('set images holder');
|
||||||
|
|
||||||
getIt.registerLazySingleton<PreviewPacksPoller>(
|
getIt.registerLazySingleton<PreviewPacksPoller>(
|
||||||
() => PreviewPacksPoller(
|
() => PreviewPacksPoller(
|
||||||
|
|
@ -56,6 +70,7 @@ Future<void> setInjections() async {
|
||||||
getIt.get<PreviewPackHolder>(),
|
getIt.get<PreviewPackHolder>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
print('set preview poller');
|
||||||
|
|
||||||
getIt.registerLazySingleton<PackManager>(
|
getIt.registerLazySingleton<PackManager>(
|
||||||
() => PackManager(
|
() => PackManager(
|
||||||
|
|
@ -66,6 +81,7 @@ Future<void> setInjections() async {
|
||||||
getIt.get<PackCacheManager>(),
|
getIt.get<PackCacheManager>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
print('set pack manager');
|
||||||
|
|
||||||
getIt.registerLazySingleton<PackUpdater>(
|
getIt.registerLazySingleton<PackUpdater>(
|
||||||
() => PackUpdater(
|
() => PackUpdater(
|
||||||
|
|
@ -75,28 +91,39 @@ Future<void> setInjections() async {
|
||||||
getIt.get<PreviewPackHolder>(),
|
getIt.get<PreviewPackHolder>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
print('set pack updater');
|
||||||
|
|
||||||
getIt.registerLazySingleton<UserManager>(
|
getIt.registerLazySingleton<UserManager>(
|
||||||
() => UserManager(
|
() => UserManager(
|
||||||
getIt.get<HttpRepository>(),
|
getIt.get<HttpRepository>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
print('set user manager');
|
||||||
|
|
||||||
getIt.registerLazySingleton<FavoriteCardsController>(
|
getIt.registerLazySingleton<FavoriteCardsController>(
|
||||||
() => FavoriteCardsController(),
|
() => FavoriteCardsController(),
|
||||||
);
|
);
|
||||||
|
print('set fav cards container');
|
||||||
|
|
||||||
getIt.registerLazySingleton<TestManager>(
|
getIt.registerLazySingleton<TestManager>(
|
||||||
() =>
|
() =>
|
||||||
TestManager(getIt.get<PackCacheManager>(), getIt.get<HttpRepository>()),
|
TestManager(getIt.get<PackCacheManager>(), getIt.get<HttpRepository>()),
|
||||||
);
|
);
|
||||||
|
print('set test manager');
|
||||||
|
|
||||||
getIt.registerLazySingleton<InAppPurchaseService>(
|
getIt.registerLazySingleton<InAppPurchaseService>(
|
||||||
() => InAppPurchaseService(),
|
() => InAppPurchaseService(),
|
||||||
);
|
);
|
||||||
|
print('set in app');
|
||||||
|
|
||||||
getIt.registerLazySingleton(
|
getIt.registerLazySingleton(
|
||||||
() => PurchaseDetailsStreamSubscription(),
|
() => PurchaseDetailsStreamSubscription(),
|
||||||
);
|
);
|
||||||
await getIt.allReady();
|
print('set purchase');
|
||||||
|
try {
|
||||||
|
await getIt.allReady();
|
||||||
|
print('injector all ready');
|
||||||
|
} catch (e, s) {
|
||||||
|
print('injector error $e $s');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
|
|
||||||
|
|
@ -89,6 +90,9 @@ class PackCacheManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> _updateSavedPacks() async {
|
Future<List<String>> _updateSavedPacks() async {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
final packsDir =
|
final packsDir =
|
||||||
Directory('${(await getApplicationDocumentsDirectory()).path}/packs');
|
Directory('${(await getApplicationDocumentsDirectory()).path}/packs');
|
||||||
List<String> savedPackIds = [];
|
List<String> savedPackIds = [];
|
||||||
|
|
@ -172,7 +176,7 @@ class PackCacheManager {
|
||||||
log('Saved ${dto.id}');
|
log('Saved ${dto.id}');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Directory> _packDirectory(String id) async => Directory(
|
Future<Directory> _packDirectory(String id) async => kIsWeb ? Directory.current : Directory(
|
||||||
'${(await getApplicationDocumentsDirectory()).path}/packs/$id',
|
'${(await getApplicationDocumentsDirectory()).path}/packs/$id',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:archive/archive.dart';
|
import 'package:archive/archive_io.dart' as a;
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:mnemo_cards/features/packs/packs_api.dart';
|
import 'package:mnemo_cards/features/packs/packs_api.dart';
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import '../../di/locator.dart';
|
||||||
import '../../features/packs/pack_cache_manager.dart';
|
import '../../features/packs/pack_cache_manager.dart';
|
||||||
import '../../features/packs/pack_holder.dart';
|
import '../../features/packs/pack_holder.dart';
|
||||||
import 'preview_pack_holder.dart';
|
import 'preview_pack_holder.dart';
|
||||||
|
|
@ -29,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: 600));
|
||||||
pollStream.listen((event) async {
|
pollStream.listen((event) async {
|
||||||
await updateAvailablePacks();
|
await updateAvailablePacks();
|
||||||
try {
|
try {
|
||||||
|
|
@ -108,20 +109,42 @@ class PackUpdater {
|
||||||
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');
|
||||||
|
final s = Stopwatch()..start();
|
||||||
final packDto = await _packsApi.getPackDto(packId);
|
final packDto = await _packsApi.getPackDto(packId);
|
||||||
|
print('get from api ${s.elapsedMilliseconds} mills');
|
||||||
if (packDto == null) {
|
if (packDto == null) {
|
||||||
throw Exception('Cant update pack $packId');
|
throw Exception('Cant update pack $packId');
|
||||||
}
|
}
|
||||||
final imagesData = await _packsApi.packImagesArchive(packId);
|
final imagesData = await _packsApi.packImagesArchive(packId);
|
||||||
Archive imagesArchive = ZipDecoder().decodeBytes(imagesData);
|
print('get cards from api ${s.elapsedMilliseconds} mills');
|
||||||
|
final userId = locator.userManager.userId!;
|
||||||
|
final appVersion = (await PackageInfo.fromPlatform()).version;
|
||||||
|
final password = TokenGenerator.generateArchivePassword(
|
||||||
|
appVersion: appVersion,
|
||||||
|
packId: packId,
|
||||||
|
);
|
||||||
|
print('generate token ${s.elapsedMilliseconds} mills');
|
||||||
|
final imagesArchive = a.ZipDecoder().decodeBytes(
|
||||||
|
imagesData,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
print('deflate archive ${s.elapsedMilliseconds} mills');
|
||||||
Map<String, MemoryImage> images = {};
|
Map<String, MemoryImage> images = {};
|
||||||
for (final file in imagesArchive) {
|
|
||||||
|
// todo optimize, make really async
|
||||||
|
Future<void> setImage(a.ArchiveFile file) async {
|
||||||
final fileData = file.content as List<int>;
|
final fileData = file.content as List<int>;
|
||||||
images[file.name] = MemoryImage(
|
final data = Uint8List.fromList(fileData);
|
||||||
Uint8List.fromList(fileData),
|
images[file.name] = MemoryImage(data);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Future.wait([
|
||||||
|
for (final file in imagesArchive) setImage(file),
|
||||||
|
]);
|
||||||
|
|
||||||
|
print('loaded images ${s.elapsedMilliseconds} mills');
|
||||||
_cacheManager.savePack(packDto, images: images);
|
_cacheManager.savePack(packDto, images: images);
|
||||||
|
print('saved pack ${s.elapsedMilliseconds} mills');
|
||||||
log('Pack ${packId} updated with cards');
|
log('Pack ${packId} updated with cards');
|
||||||
return packDto;
|
return packDto;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class PreviewPacksPoller {
|
||||||
PreviewPacksPoller(this._api, this._previewPackHolder);
|
PreviewPacksPoller(this._api, this._previewPackHolder);
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
Stream.periodic(Duration(seconds: 60), (_) async {
|
Stream.periodic(Duration(seconds: 600), (_) async {
|
||||||
poll();
|
poll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,10 +124,6 @@ class _ProgressWidgetState extends State<ProgressWidget> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (kDebugMode)
|
|
||||||
Text(
|
|
||||||
'${(results.where((r) => r != Result.not_visited).length)}/${results.length}',
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,8 @@ class _TestPageState extends State<TestPage> {
|
||||||
print('AUDIO $_lastAudioQuestionId ${question.id}');
|
print('AUDIO $_lastAudioQuestionId ${question.id}');
|
||||||
_lastAudioQuestionId = question.id;
|
_lastAudioQuestionId = question.id;
|
||||||
try {
|
try {
|
||||||
if (globalSharedPreferences.getBool('auto_play_sound_tests') != false) {
|
if (globalSharedPreferences.getBool('auto_play_sound_tests') !=
|
||||||
|
false) {
|
||||||
AudioPlayer.playAudio((question as dynamic).audio);
|
AudioPlayer.playAudio((question as dynamic).audio);
|
||||||
}
|
}
|
||||||
} catch (e, s) {}
|
} catch (e, s) {}
|
||||||
|
|
@ -156,39 +157,7 @@ class _TestPageState extends State<TestPage> {
|
||||||
TestQuestionType.undefined =>
|
TestQuestionType.undefined =>
|
||||||
throw UnimplementedError(),
|
throw UnimplementedError(),
|
||||||
};
|
};
|
||||||
}.let(
|
},
|
||||||
(builder) => (c, i) =>
|
|
||||||
builder.call(c, i)?.let((w) => kDebugMode
|
|
||||||
? Stack(
|
|
||||||
children: [
|
|
||||||
w,
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text('$i'),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
locator.testManager
|
|
||||||
.pageController
|
|
||||||
.animateToPage(
|
|
||||||
locator
|
|
||||||
.testManager
|
|
||||||
.pageController
|
|
||||||
.page!
|
|
||||||
.toInt() +
|
|
||||||
1,
|
|
||||||
duration: Duration(
|
|
||||||
milliseconds: 500),
|
|
||||||
curve: Curves.easeIn,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Icons.next_plan_outlined))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: w),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'dart:math' as m;
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mnemo_cards/features/packs/images_holder.dart';
|
import 'package:mnemo_cards/features/packs/images_holder.dart';
|
||||||
import 'package:mnemo_cards/theme/themes.dart';
|
import 'package:mnemo_cards/theme/themes.dart';
|
||||||
|
|
||||||
|
|
@ -95,8 +96,8 @@ class TestImage {
|
||||||
if (data.length > 50) {
|
if (data.length > 50) {
|
||||||
Uint8List? bytes;
|
Uint8List? bytes;
|
||||||
try {
|
try {
|
||||||
bytes = base64Decode(data);
|
bytes = await compute(base64Decode, data);
|
||||||
return MemoryImage(bytes);
|
return MemoryImage(bytes!);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('Cant decode base64 test image');
|
log('Cant decode base64 test image');
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,15 @@ import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:jailbreak_root_detection/jailbreak_root_detection.dart';
|
import 'package:jailbreak_root_detection/jailbreak_root_detection.dart';
|
||||||
import 'package:mnemo_cards/di/locator.dart';
|
import 'package:mnemo_cards/di/locator.dart';
|
||||||
import 'package:mnemo_cards/domain/router/app_router.dart';
|
import 'package:mnemo_cards/domain/router/app_router.dart';
|
||||||
|
import 'package:mnemo_cards/managers/repository/certificates.dart';
|
||||||
import 'package:mnemo_cards/managers/repository/firebase_config_repository.dart';
|
import 'package:mnemo_cards/managers/repository/firebase_config_repository.dart';
|
||||||
import 'package:mnemo_cards/features/packs/pack_manager.dart';
|
import 'package:mnemo_cards/features/packs/pack_manager.dart';
|
||||||
import 'package:mnemo_cards/theme/themes.dart';
|
import 'package:mnemo_cards/theme/themes.dart';
|
||||||
|
|
@ -30,6 +33,7 @@ import 'managers/repository/repository.dart';
|
||||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||||
late final SharedPreferences globalSharedPreferences;
|
late final SharedPreferences globalSharedPreferences;
|
||||||
|
late final FlutterSecureStorage secureStorage;
|
||||||
|
|
||||||
final appRouter = AppRouter(ADMIN_BUILD);
|
final appRouter = AppRouter(ADMIN_BUILD);
|
||||||
|
|
||||||
|
|
@ -45,7 +49,7 @@ void main() async {
|
||||||
SystemChrome.setPreferredOrientations([
|
SystemChrome.setPreferredOrientations([
|
||||||
DeviceOrientation.portraitUp,
|
DeviceOrientation.portraitUp,
|
||||||
]);
|
]);
|
||||||
androidDeviceInfo = await DeviceInfoPlugin().androidInfo;
|
androidDeviceInfo = kIsWeb ? null : await DeviceInfoPlugin().androidInfo;
|
||||||
print('deviceInfo ${stopwatch.elapsedMilliseconds}');
|
print('deviceInfo ${stopwatch.elapsedMilliseconds}');
|
||||||
final isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
|
final isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
|
||||||
print('No trunst ${stopwatch.elapsedMilliseconds}');
|
print('No trunst ${stopwatch.elapsedMilliseconds}');
|
||||||
|
|
@ -65,7 +69,13 @@ void main() async {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
secureStorage = FlutterSecureStorage(
|
||||||
|
aOptions: const AndroidOptions(
|
||||||
|
encryptedSharedPreferences: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
globalSharedPreferences = await SharedPreferences.getInstance();
|
globalSharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
await _fillSp();
|
await _fillSp();
|
||||||
print('global sp ${stopwatch.elapsedMilliseconds}');
|
print('global sp ${stopwatch.elapsedMilliseconds}');
|
||||||
await setInjections();
|
await setInjections();
|
||||||
|
|
@ -81,32 +91,38 @@ void main() async {
|
||||||
await locator.previewPackPoller.init();
|
await locator.previewPackPoller.init();
|
||||||
await locator.packUpdater.init();
|
await locator.packUpdater.init();
|
||||||
print('initing complete ${stopwatch.elapsedMilliseconds}');
|
print('initing complete ${stopwatch.elapsedMilliseconds}');
|
||||||
await MobileAds.initialize();
|
if (!kIsWeb) await MobileAds.initialize();
|
||||||
print('mobile ads ${stopwatch.elapsedMilliseconds}');
|
print('mobile ads ${stopwatch.elapsedMilliseconds}');
|
||||||
print('runApp ${stopwatch.elapsedMilliseconds}');
|
print('runApp ${stopwatch.elapsedMilliseconds}');
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
} on Object catch (e, s) {
|
} on Object catch (e, s) {
|
||||||
|
print('err: ${e.toString()}');
|
||||||
|
print('stacktrace: ${s.toString()}');
|
||||||
log(e.toString(), stackTrace: s);
|
log(e.toString(), stackTrace: s);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fillSp() async {
|
Future<void> _fillSp() async {
|
||||||
|
final showAdmin = globalSharedPreferences.getBool('show_admin');
|
||||||
|
if (showAdmin == null && ADMIN_BUILD) {
|
||||||
|
await globalSharedPreferences.setBool('show_admin', true);
|
||||||
|
}
|
||||||
final soundOn = globalSharedPreferences.getBool('sound_on');
|
final soundOn = globalSharedPreferences.getBool('sound_on');
|
||||||
if (soundOn == null) {
|
if (soundOn == null) {
|
||||||
globalSharedPreferences.setBool('sound_on', true);
|
await globalSharedPreferences.setBool('sound_on', true);
|
||||||
}
|
}
|
||||||
final soundSpeed = globalSharedPreferences.getDouble('sound_speed');
|
final soundSpeed = globalSharedPreferences.getDouble('sound_speed');
|
||||||
if (soundSpeed == null) {
|
if (soundSpeed == null) {
|
||||||
globalSharedPreferences.setDouble('sound_speed', 1.0);
|
await globalSharedPreferences.setDouble('sound_speed', 1.0);
|
||||||
}
|
}
|
||||||
final autoPlay = globalSharedPreferences.getBool('auto_play_sound_tests');
|
final autoPlay = globalSharedPreferences.getBool('auto_play_sound_tests');
|
||||||
if (autoPlay == null) {
|
if (autoPlay == null) {
|
||||||
globalSharedPreferences.setBool('auto_play_sound_tests', true);
|
await globalSharedPreferences.setBool('auto_play_sound_tests', true);
|
||||||
}
|
}
|
||||||
final autoPlayView = globalSharedPreferences.getBool('auto_play_sound_view');
|
final autoPlayView = globalSharedPreferences.getBool('auto_play_sound_view');
|
||||||
if (autoPlayView == null) {
|
if (autoPlayView == null) {
|
||||||
globalSharedPreferences.setBool('auto_play_sound_view', false);
|
await globalSharedPreferences.setBool('auto_play_sound_view', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,6 +151,7 @@ class MyApp extends StatelessWidget with WidgetsBindingObserver {
|
||||||
theme: lightTheme,
|
theme: lightTheme,
|
||||||
routerConfig: appRouter.config(),
|
routerConfig: appRouter.config(),
|
||||||
scaffoldMessengerKey: scaffoldMessengerKey,
|
scaffoldMessengerKey: scaffoldMessengerKey,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
lib/managers/repository/certificates.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class Certificates {
|
||||||
|
static get spKey =>
|
||||||
|
'qAcZM68Q7cR6gmxvB0ar0tr6axm520BNRLv66vmpWZnMHLCmzvEKNZkqL1VD1XHt';
|
||||||
|
|
||||||
|
static final crt = utf8.encode(
|
||||||
|
'''
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIB8zCCAZmgAwIBAgIUHgV3sAY8MClrgLTwmO5OG4JsMyAwCgYIKoZIzj0EAwIw
|
||||||
|
UTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxFDASBgNVBAoMC01u
|
||||||
|
ZW1vIGNhcmRzMRcwFQYDVQQDDA4xOTIuMTY4LjMxLjE1ODAeFw0yNDA1MTgxMTUy
|
||||||
|
MTJaFw0yNzAyMTIxMTUyMTJaMF4xCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l
|
||||||
|
LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFzAVBgNV
|
||||||
|
BAMMDjE5Mi4xNjguMzEuMTU4MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvO1/
|
||||||
|
PU/ayMFgZw9KBLKpLHUpKQseY6Q6R4G51DaT8LBMfxMgAALf7PMj5IMNcW8ThjLY
|
||||||
|
MpYL8jZhufOtxCXdPKNCMEAwHQYDVR0OBBYEFBHLVqG6EfK9nrOeE1+JpUcUiEHv
|
||||||
|
MB8GA1UdIwQYMBaAFDJb2MW2vBxpKETey+ful5v7Jp3nMAoGCCqGSM49BAMCA0gA
|
||||||
|
MEUCIDwUzh9oXPE2xhArNimIpkCNyJViUf7B4kt9dgZtMLAtAiEAg3rqCqZXyys4
|
||||||
|
mozimzUqDNc811KH3IFhC/Je4zQ3g68=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
'''
|
||||||
|
.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
static final key = utf8.encode(
|
||||||
|
'''
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEIDFGWXnWiqxMCYs34r5pJq2YVT3x1MoA07fnaj9iy1SYoAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAEvO1/PU/ayMFgZw9KBLKpLHUpKQseY6Q6R4G51DaT8LBMfxMgAALf
|
||||||
|
7PMj5IMNcW8ThjLYMpYL8jZhufOtxCXdPA==
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
|
'''
|
||||||
|
.trim(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
|
@ -8,6 +9,7 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:mnemo_cards/domain/router/app_router.gr.dart';
|
import 'package:mnemo_cards/domain/router/app_router.gr.dart';
|
||||||
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:http_certificate_pinning/http_certificate_pinning.dart';
|
import 'package:http_certificate_pinning/http_certificate_pinning.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
@ -24,18 +26,11 @@ class DioProvider {
|
||||||
Dio get dio => _dio;
|
Dio get dio => _dio;
|
||||||
|
|
||||||
Future<DioProvider> init() async {
|
Future<DioProvider> init() async {
|
||||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
PackageInfo appPackageInfo = await PackageInfo.fromPlatform();
|
||||||
String appVersion = packageInfo.version;
|
String appVersion = appPackageInfo.version;
|
||||||
|
|
||||||
_dio = Dio()
|
_dio = Dio()
|
||||||
..interceptors.addAll([
|
..interceptors.addAll([
|
||||||
InterceptorsWrapper(onResponse: (response, handler) {
|
|
||||||
// if (response.data is List<int>) {
|
|
||||||
// handler.next(response..data = utf8.decode(response.data));
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
handler.next(response);
|
|
||||||
}),
|
|
||||||
// CertificatePinningInterceptor(allowedSHAFingerprints: [
|
// CertificatePinningInterceptor(allowedSHAFingerprints: [
|
||||||
// '28:21:5C:FB:54:4F:4A:DC:F8:5E:4C:FE:44:C8:E0:7B:0B:09:9D:D0:A9:FA:60:1D:3B:03:FF:EC:25:1A:C5:CC',
|
// '28:21:5C:FB:54:4F:4A:DC:F8:5E:4C:FE:44:C8:E0:7B:0B:09:9D:D0:A9:FA:60:1D:3B:03:FF:EC:25:1A:C5:CC',
|
||||||
// ]),
|
// ]),
|
||||||
|
|
@ -63,42 +58,60 @@ class DioProvider {
|
||||||
// }
|
// }
|
||||||
handler.next(exception);
|
handler.next(exception);
|
||||||
}),
|
}),
|
||||||
InterceptorsWrapper(onRequest: (request, handler) {
|
InterceptorsWrapper(onRequest: (request, handler) async {
|
||||||
request.headers = {...request.headers, 'app_version': appVersion};
|
final String? requestToken;
|
||||||
|
try {
|
||||||
|
requestToken = TokenGenerator.generateRequestToken(
|
||||||
|
requestBody: (request.data as Object?)?.encode() ?? '',
|
||||||
|
appVersion: appVersion,
|
||||||
|
requestPath: request.uri.path,
|
||||||
|
userToken: await locator.userManager.getAuthToken(),
|
||||||
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
log(
|
||||||
|
'Exception while generating request token',
|
||||||
|
error: e,
|
||||||
|
stackTrace: s,
|
||||||
|
);
|
||||||
|
throw Exception(
|
||||||
|
'Exception while generating request token ${e} ${s}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
request.headers = {
|
||||||
|
...request.headers,
|
||||||
|
AppHeaders.appVersion: appVersion,
|
||||||
|
AppHeaders.requestToken: requestToken,
|
||||||
|
};
|
||||||
handler.next(request);
|
handler.next(request);
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// final sslCert = await rootBundle.load('assets/ca.crt');
|
if (!kIsWeb) {
|
||||||
// final clientCert = await rootBundle.load('assets/client.crt');
|
final securityContext = SecurityContext(withTrustedRoots: true);
|
||||||
// final sslKey = await rootBundle.load('assets/client.key');
|
|
||||||
SecurityContext securityContext = SecurityContext(withTrustedRoots: true);
|
|
||||||
// securityContext.setTrustedCertificatesBytes(
|
|
||||||
// sslCert.buffer.asInt8List(),
|
|
||||||
// );
|
|
||||||
// securityContext.setTrustedCertificatesBytes(
|
|
||||||
// clientCert.buffer.asInt8List(),
|
|
||||||
// );
|
|
||||||
// securityContext.usePrivateKeyBytes(sslKey.buffer.asInt8List());
|
|
||||||
|
|
||||||
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
|
// securityContext.setTrustedCertificatesBytes(Certificates.crt);
|
||||||
HttpClient httpClient = HttpClient(context: securityContext)
|
// securityContext.usePrivateKeyBytes(Certificates.key);
|
||||||
..badCertificateCallback =
|
|
||||||
(X509Certificate cert, String host, int port) {
|
_dio.httpClientAdapter = IOHttpClientAdapter(
|
||||||
print(cert.issuer);
|
createHttpClient: () {
|
||||||
print('$host $port');
|
HttpClient httpClient = HttpClient(context: securityContext)
|
||||||
final now = DateTime.now();
|
..badCertificateCallback =
|
||||||
final valid =
|
(X509Certificate cert, String host, int port) {
|
||||||
cert.startValidity.isBefore(now) && cert.endValidity.isAfter(now);
|
print(cert.issuer);
|
||||||
return valid &&
|
print('$host $port');
|
||||||
const DeepCollectionEquality().equals(
|
final now = DateTime.now();
|
||||||
cert.sha1,
|
final valid = cert.startValidity.isBefore(now) &&
|
||||||
_cert,
|
cert.endValidity.isAfter(now);
|
||||||
);
|
return valid &&
|
||||||
return true;
|
const DeepCollectionEquality().equals(
|
||||||
};
|
cert.sha1,
|
||||||
return httpClient;
|
_cert,
|
||||||
};
|
);
|
||||||
|
};
|
||||||
|
return httpClient;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class HttpRepository extends Repository with Api {
|
||||||
void setAuthToken(String? authToken) {
|
void setAuthToken(String? authToken) {
|
||||||
_dio.interceptors.add(
|
_dio.interceptors.add(
|
||||||
InterceptorsWrapper(onRequest: (options, handler) {
|
InterceptorsWrapper(onRequest: (options, handler) {
|
||||||
options.headers[HttpHeaders.authorizationHeader] = authToken;
|
options.headers[AppHeaders.userToken] = authToken;
|
||||||
handler.next(options);
|
handler.next(options);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ class UserManager {
|
||||||
|
|
||||||
bool get hasUser => userStateHolder.user != null;
|
bool get hasUser => userStateHolder.user != null;
|
||||||
|
|
||||||
|
int? get userId => userStateHolder.user?.id;
|
||||||
|
|
||||||
Future<void> login() async {
|
Future<void> login() async {
|
||||||
if (await _googleSignIn.isSignedIn()) {
|
if (await _googleSignIn.isSignedIn()) {
|
||||||
await _googleSignIn.signOut();
|
await _googleSignIn.signOut();
|
||||||
|
|
@ -61,17 +63,21 @@ class UserManager {
|
||||||
) async {
|
) async {
|
||||||
final (user, token) =
|
final (user, token) =
|
||||||
await _repository.createOrGetUser(externalId, idType, name);
|
await _repository.createOrGetUser(externalId, idType, name);
|
||||||
_setAuthToken(token);
|
await _setAuthToken(token);
|
||||||
userStateHolder.setUser(user);
|
userStateHolder.setUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setAuthToken(String authToken) async {
|
Future<void> _setAuthToken(String authToken) async {
|
||||||
_repository.setAuthToken(authToken);
|
_repository.setAuthToken(authToken);
|
||||||
await _sharedPreferences!.setString('authToken', authToken);
|
await secureStorage.write(key: 'authToken', value: authToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _clearAuthToken() async {
|
Future<void> _clearAuthToken() async {
|
||||||
await _sharedPreferences!.remove('authToken');
|
await secureStorage.delete(key: 'authToken');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getAuthToken() async {
|
||||||
|
return secureStorage.read(key: 'authToken');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PromoCodeDto> applyPromocode(String code) async {
|
Future<PromoCodeDto> applyPromocode(String code) async {
|
||||||
|
|
@ -80,9 +86,9 @@ class UserManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UserDto?> updateUser() async {
|
Future<UserDto?> updateUser() async {
|
||||||
String? authToken = _sharedPreferences!.getString('authToken');
|
String? authToken = await getAuthToken();
|
||||||
if (authToken != null) {
|
if (authToken != null) {
|
||||||
_setAuthToken(authToken);
|
await _setAuthToken(authToken);
|
||||||
try {
|
try {
|
||||||
final fetchedUser = await _repository.getUser();
|
final fetchedUser = await _repository.getUser();
|
||||||
userStateHolder.setUser(fetchedUser!);
|
userStateHolder.setUser(fetchedUser!);
|
||||||
|
|
@ -117,40 +123,44 @@ class UserManager {
|
||||||
.listen((account) async {
|
.listen((account) async {
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
final name = account.displayName;
|
final name = account.displayName;
|
||||||
final token =
|
final externalToken =
|
||||||
await account.authentication.then((value) => value.idToken);
|
await account.authentication.then((value) => value.idToken);
|
||||||
Analytics.login(ExternalIdType.google);
|
Analytics.login(ExternalIdType.google);
|
||||||
await _createUser(token!, ExternalIdType.google, name);
|
await _createUser(externalToken!, ExternalIdType.google, name);
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
..add(userStateHolder.asNullableStream
|
..add(
|
||||||
.distinct((a, b) => a?.id == b?.id)
|
userStateHolder.asNullableStream
|
||||||
.listen((user) async {
|
.distinct((a, b) => a?.id == b?.id)
|
||||||
try {
|
// don't want to clear cache on login
|
||||||
if (user == null) {
|
.skip(1)
|
||||||
AppRouter.openAuthOrProfile();
|
.listen(
|
||||||
} else {
|
(user) async {
|
||||||
try {
|
try {
|
||||||
AppRouter.closeAuthPage();
|
if (user == null) {
|
||||||
await locator.previewPackPoller.poll();
|
AppRouter.openAuthOrProfile();
|
||||||
await locator.packManager.clearCache();
|
} else {
|
||||||
await locator.packUpdater.updateAvailablePacks();
|
try {
|
||||||
} catch (err) {
|
AppRouter.closeAuthPage();
|
||||||
log(err.toString());
|
await locator.previewPackPoller.poll();
|
||||||
|
await locator.packManager.clearCache();
|
||||||
|
await locator.packUpdater.updateAvailablePacks();
|
||||||
|
} catch (err) {
|
||||||
|
log(err.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e.toString());
|
||||||
|
_clearAuthToken();
|
||||||
|
if (e is DioException) {
|
||||||
|
if (e.response?.statusCode == 401) {
|
||||||
|
AppRouter.openProfile();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
} catch (e) {
|
),
|
||||||
log(e.toString());
|
);
|
||||||
_clearAuthToken();
|
|
||||||
if (e is DioException) {
|
|
||||||
if (e.response?.statusCode == 401) {
|
|
||||||
appRouter.push(
|
|
||||||
PageRouteInfo(ProfilePage.name),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class AuthPage extends StatelessWidget {
|
||||||
fit: BoxFit.fitWidth,
|
fit: BoxFit.fitWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (SHOW_ADMIN)
|
if (ADMIN_BUILD)
|
||||||
SharedPrefButton<bool>(
|
SharedPrefButton<bool>(
|
||||||
builder: (v, _) => (v ?? false) ? Text('PROD') : Text('TEST'),
|
builder: (v, _) => (v ?? false) ? Text('PROD') : Text('TEST'),
|
||||||
spKey: 'env',
|
spKey: 'env',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/foundation.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/main.dart';
|
import 'package:mnemo_cards/main.dart';
|
||||||
|
|
@ -270,6 +271,9 @@ class _AdTile extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (locator.userManager.userStateHolder.user?.subscription == true)
|
||||||
|
return SizedBox.shrink();
|
||||||
|
if (kIsWeb) return SizedBox.shrink();
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
final banner = YandexAds.createBanner(
|
final banner = YandexAds.createBanner(
|
||||||
BoxConstraints.tight(Size(constraints.maxWidth - 16, 80 - 8)));
|
BoxConstraints.tight(Size(constraints.maxWidth - 16, 80 - 8)));
|
||||||
|
|
|
||||||
|
|
@ -78,24 +78,32 @@ class _PacksMenuWidgetState extends State<PacksMenuWidget> {
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
color: snapshot.data?.firstOrNull?.color?.asColor ??
|
color: snapshot.data?.firstOrNull?.color?.asColor ??
|
||||||
Colors.black,
|
Colors.black,
|
||||||
child: ListView(
|
child: (!snapshot.hasData)
|
||||||
physics: BouncingScrollPhysics(
|
? Center(
|
||||||
decelerationRate: ScrollDecelerationRate.fast,
|
child: Column(
|
||||||
parent: AlwaysScrollableScrollPhysics()),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
scrollDirection: Axis.vertical,
|
children: [
|
||||||
children: [
|
Padding(
|
||||||
if (snapshot.data == null)
|
padding: const EdgeInsets.all(8.0),
|
||||||
...[1, 2, 3, 4].map(
|
child: Image.asset(
|
||||||
(e) => _PackButton4Shimmer(
|
'images/cerdo.jpg',
|
||||||
constraints.maxWidth,
|
width: 100,
|
||||||
cardHeight,
|
),
|
||||||
|
),
|
||||||
|
Text('Загрузка...'),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: ListView(
|
||||||
|
physics: BouncingScrollPhysics(
|
||||||
|
decelerationRate: ScrollDecelerationRate.fast,
|
||||||
|
parent: AlwaysScrollableScrollPhysics(),
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
children: snapshot.requireData!
|
||||||
|
.map((pack) => _PackButton4(pack, cardHeight))
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
if (snapshot.hasData)
|
|
||||||
...snapshot.data!
|
|
||||||
.map((pack) => _PackButton4(pack, cardHeight)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -110,7 +118,7 @@ class _PacksMenuWidgetState extends State<PacksMenuWidget> {
|
||||||
for (final pack in packs) {
|
for (final pack in packs) {
|
||||||
if (pack.imageBase64 != null) {
|
if (pack.imageBase64 != null) {
|
||||||
try {
|
try {
|
||||||
final bytes = base64Decode(pack.imageBase64!);
|
final bytes = await compute(base64Decode, pack.imageBase64!);
|
||||||
final image = MemoryImage(bytes);
|
final image = MemoryImage(bytes);
|
||||||
locator.imagesHolder.setImage('pack_${pack.id}', image);
|
locator.imagesHolder.setImage('pack_${pack.id}', image);
|
||||||
await precacheImage(image, context);
|
await precacheImage(image, context);
|
||||||
|
|
@ -165,18 +173,11 @@ class _PackButton4 extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// border: Border.all(
|
|
||||||
// color: pack.dto.color?.asColor?.withOpacity(0.9) ??
|
|
||||||
// Colors.grey.withOpacity(0.5),
|
|
||||||
// ),
|
|
||||||
// borderRadius: BorderRadius.circular(12.0),
|
|
||||||
image: pack.imageBase64 == null
|
image: pack.imageBase64 == null
|
||||||
? null
|
? null
|
||||||
: DecorationImage(
|
: DecorationImage(
|
||||||
image: locator.imagesHolder.get('pack_${pack.id}') ??
|
image: locator.imagesHolder.get('pack_${pack.id}') ??
|
||||||
MemoryImage(
|
MemoryImage(base64Decode(pack.imageBase64!)),
|
||||||
base64Decode(pack.imageBase64!),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
margin: EdgeInsets.all(2.0.w),
|
margin: EdgeInsets.all(2.0.w),
|
||||||
|
|
@ -253,125 +254,3 @@ class _PackButton4 extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PackButton4Shimmer extends StatelessWidget {
|
|
||||||
final double cardHeight;
|
|
||||||
final double cardWidth;
|
|
||||||
|
|
||||||
const _PackButton4Shimmer(
|
|
||||||
this.cardWidth,
|
|
||||||
this.cardHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Shimmer.fromColors(
|
|
||||||
baseColor: Colors.white,
|
|
||||||
highlightColor: borderGray,
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
height: cardHeight,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.grey.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
color: Colors.white,
|
|
||||||
width: cardHeight,
|
|
||||||
height: cardHeight,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 30,
|
|
||||||
width: (cardWidth - cardHeight) * 0.7,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 20,
|
|
||||||
width: (cardWidth - cardHeight) * 0.5,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 20,
|
|
||||||
width: (cardWidth - cardHeight) * 0.1,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 25,
|
|
||||||
width: (cardWidth - cardHeight) * 0.1,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PackButtonShimmer extends StatelessWidget {
|
|
||||||
final double cardWidth;
|
|
||||||
final double cardHeight;
|
|
||||||
|
|
||||||
const _PackButtonShimmer(
|
|
||||||
this.cardWidth,
|
|
||||||
this.cardHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Shimmer.fromColors(
|
|
||||||
baseColor: Colors.white,
|
|
||||||
highlightColor: Colors.grey[200]!,
|
|
||||||
child: Container(
|
|
||||||
width: cardWidth,
|
|
||||||
height: cardHeight,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
width: cardWidth,
|
|
||||||
height: cardWidth,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
|
||||||
)),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
width: cardWidth * 0.7,
|
|
||||||
height: 30,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
52
pubspec.lock
|
|
@ -494,6 +494,54 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.9.0"
|
version: "5.9.0"
|
||||||
|
flutter_secure_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage
|
||||||
|
sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.2.2"
|
||||||
|
flutter_secure_storage_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_linux
|
||||||
|
sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
flutter_secure_storage_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_macos
|
||||||
|
sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
flutter_secure_storage_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_platform_interface
|
||||||
|
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_secure_storage_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_web
|
||||||
|
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
flutter_secure_storage_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_windows
|
||||||
|
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -732,10 +780,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: js
|
name: js
|
||||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.1"
|
version: "0.6.7"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
53
pubspec.yaml
|
|
@ -1,32 +1,12 @@
|
||||||
name: mnemo_cards
|
name: mnemo_cards
|
||||||
description: Mnemo
|
description: Mnemo
|
||||||
# The following line prevents the package from being accidentally published to
|
publish_to: 'none'
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
version: 1.0.1
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
|
||||||
# followed by an optional build number separated by a +.
|
|
||||||
# Both the version and the builder number may be overridden in flutter
|
|
||||||
# build by specifying --build-name and --build-number, respectively.
|
|
||||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
||||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
|
||||||
# Read more about iOS versioning at
|
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
|
||||||
version: 1.0.0+6
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.3 <4.0.0'
|
sdk: '>=3.1.3 <4.0.0'
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
|
||||||
# versions available, run `flutter pub outdated`.
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
@ -35,11 +15,12 @@ dependencies:
|
||||||
path: ../mnemo_cards_common
|
path: ../mnemo_cards_common
|
||||||
|
|
||||||
get_it:
|
get_it:
|
||||||
shared_preferences:
|
shared_preferences: ^2.2.3
|
||||||
|
flutter_secure_storage: ^9.2.2
|
||||||
rxdart:
|
rxdart:
|
||||||
firebase_core: ^2.30.1
|
firebase_core: ^2.30.1
|
||||||
# firebase_crashlytics:
|
# firebase_crashlytics:
|
||||||
# firebase_storage:
|
# firebase_storage:
|
||||||
cloud_firestore: ^4.17.2
|
cloud_firestore: ^4.17.2
|
||||||
freezed:
|
freezed:
|
||||||
json_serializable:
|
json_serializable:
|
||||||
|
|
@ -78,6 +59,8 @@ dependencies:
|
||||||
http_certificate_pinning: ^2.1.3
|
http_certificate_pinning: ^2.1.3
|
||||||
url_launcher: ^6.2.6
|
url_launcher: ^6.2.6
|
||||||
|
|
||||||
|
# requests_inspector: ^4.0.3
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
@ -103,11 +86,11 @@ flutter:
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: "launcher_icon"
|
android: "launcher_icon"
|
||||||
ios: true
|
ios: true
|
||||||
image_path: "images/cerdo.jpg"
|
image_path: "images/icon.png"
|
||||||
min_sdk_android: 21 # android min sdk min:16, default 21
|
min_sdk_android: 21 # android min sdk min:16, default 21
|
||||||
|
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
# Only one parameter can be used, color and background_image cannot both be set.
|
# Only one parameter can be used, color and background_image cannot both be set.
|
||||||
color: "#ffffff"
|
color: "#ffffff"
|
||||||
#background_image: "assets/background.png"
|
#background_image: "assets/background.png"
|
||||||
|
|
||||||
|
|
@ -141,16 +124,16 @@ flutter_native_splash:
|
||||||
# Please visit https://developer.android.com/guide/topics/ui/splash-screen
|
# Please visit https://developer.android.com/guide/topics/ui/splash-screen
|
||||||
# Following are specific parameters for Android 12+.
|
# Following are specific parameters for Android 12+.
|
||||||
android_12:
|
android_12:
|
||||||
# The image parameter sets the splash screen icon image. If this parameter is not specified,
|
# The image parameter sets the splash screen icon image. If this parameter is not specified,
|
||||||
# the app's launcher icon will be used instead.
|
# the app's launcher icon will be used instead.
|
||||||
# Please note that the splash screen will be clipped to a circle on the center of the screen.
|
# Please note that the splash screen will be clipped to a circle on the center of the screen.
|
||||||
# App icon with an icon background: This should be 960×960 pixels, and fit within a circle
|
# App icon with an icon background: This should be 960×960 pixels, and fit within a circle
|
||||||
# 640 pixels in diameter.
|
# 640 pixels in diameter.
|
||||||
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
|
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
|
||||||
# 768 pixels in diameter.
|
# 768 pixels in diameter.
|
||||||
image: images/cerdo_big.jpg
|
image: images/cerdo_big.jpg
|
||||||
|
|
||||||
# Splash screen background color.
|
# Splash screen background color.
|
||||||
color: "#ffffff"
|
color: "#ffffff"
|
||||||
|
|
||||||
# App icon background color.
|
# App icon background color.
|
||||||
|
|
|
||||||