This commit is contained in:
Dmitry 2024-06-21 22:12:29 +03:00
parent e8b0d87b2e
commit 367eab45e9
48 changed files with 335 additions and 326 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View file

@ -44,7 +44,7 @@ class _AllCardsState extends State<AllCards> {
_cache.clear();
while (loadingIds.isNotEmpty) {
try {
final ids = loadingIds.take(20).toList();
final ids = loadingIds.take(10).toList();
loadingIds.removeRange(0, ids.length);
final cardsPage = await api.getCards(ids);
for (final card in cardsPage) {

View file

@ -24,31 +24,45 @@ import '../managers/user_manager.dart';
GetIt getIt = GetIt.I;
Future<void> setInjections() async {
getIt.registerSingletonAsync<DioProvider>(
() => DioProvider().init(),
);
final _dio = await DioProvider().init();
getIt.registerSingleton<DioProvider>(_dio);
print('set dio');
getIt.registerLazySingleton<HttpRepository>(
() => HttpRepository(getIt.get<DioProvider>().dio),
);
print('set repo');
getIt.registerLazySingleton<PacksApi>(
() => PacksApi(getIt.get<DioProvider>().dio),
);
if (ADMIN_BUILD)
print('set pack');
if (ADMIN_BUILD) {
getIt.registerLazySingleton<AdminApi>(
() => AdminApi(getIt.get<DioProvider>().dio),
);
() => AdminApi(getIt.get<DioProvider>().dio));
print('set admin');
}
getIt.registerLazySingleton<PackHolder>(
() => PackHolder(),
);
print('set pack holder');
getIt.registerLazySingleton<PreviewPackHolder>(
() => PreviewPackHolder(),
);
print('set preview');
getIt.registerLazySingleton<PackCacheManager>(
() => PackCacheManager(),
);
print('set cache');
getIt.registerLazySingleton<ImagesHolder>(
() => ImagesHolder(),
);
print('set images holder');
getIt.registerLazySingleton<PreviewPacksPoller>(
() => PreviewPacksPoller(
@ -56,6 +70,7 @@ Future<void> setInjections() async {
getIt.get<PreviewPackHolder>(),
),
);
print('set preview poller');
getIt.registerLazySingleton<PackManager>(
() => PackManager(
@ -66,6 +81,7 @@ Future<void> setInjections() async {
getIt.get<PackCacheManager>(),
),
);
print('set pack manager');
getIt.registerLazySingleton<PackUpdater>(
() => PackUpdater(
@ -75,28 +91,39 @@ Future<void> setInjections() async {
getIt.get<PreviewPackHolder>(),
),
);
print('set pack updater');
getIt.registerLazySingleton<UserManager>(
() => UserManager(
getIt.get<HttpRepository>(),
),
);
print('set user manager');
getIt.registerLazySingleton<FavoriteCardsController>(
() => FavoriteCardsController(),
);
print('set fav cards container');
getIt.registerLazySingleton<TestManager>(
() =>
TestManager(getIt.get<PackCacheManager>(), getIt.get<HttpRepository>()),
);
print('set test manager');
getIt.registerLazySingleton<InAppPurchaseService>(
() => InAppPurchaseService(),
);
print('set in app');
getIt.registerLazySingleton(
() => PurchaseDetailsStreamSubscription(),
);
await getIt.allReady();
print('set purchase');
try {
await getIt.allReady();
print('injector all ready');
} catch (e, s) {
print('injector error $e $s');
}
}

View file

@ -4,6 +4,7 @@ import 'dart:developer';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
@ -89,6 +90,9 @@ class PackCacheManager {
}
Future<List<String>> _updateSavedPacks() async {
if (kIsWeb) {
return [];
}
final packsDir =
Directory('${(await getApplicationDocumentsDirectory()).path}/packs');
List<String> savedPackIds = [];
@ -172,7 +176,7 @@ class PackCacheManager {
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',
);
}

View file

@ -2,11 +2,12 @@ import 'dart:async';
import 'dart:developer';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:archive/archive_io.dart' as a;
import 'package:flutter/cupertino.dart';
import 'package:mnemo_cards/features/packs/packs_api.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_holder.dart';
import 'preview_pack_holder.dart';
@ -29,7 +30,7 @@ class PackUpdater {
);
Future<void> init() async {
final pollStream = Stream.periodic(const Duration(seconds: 60));
final pollStream = Stream.periodic(const Duration(seconds: 600));
pollStream.listen((event) async {
await updateAvailablePacks();
try {
@ -108,20 +109,42 @@ class PackUpdater {
Future<CardPackDto> updatePackAndSaveImages(String packId) async {
try {
log('Updating with cards ${packId}', name: 'updatePackWithCards');
final s = Stopwatch()..start();
final packDto = await _packsApi.getPackDto(packId);
print('get from api ${s.elapsedMilliseconds} mills');
if (packDto == null) {
throw Exception('Cant update pack $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 = {};
for (final file in imagesArchive) {
// todo optimize, make really async
Future<void> setImage(a.ArchiveFile file) async {
final fileData = file.content as List<int>;
images[file.name] = MemoryImage(
Uint8List.fromList(fileData),
);
final data = 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);
print('saved pack ${s.elapsedMilliseconds} mills');
log('Pack ${packId} updated with cards');
return packDto;
} catch (e, s) {

View file

@ -12,7 +12,7 @@ class PreviewPacksPoller {
PreviewPacksPoller(this._api, this._previewPackHolder);
Future<void> init() async {
Stream.periodic(Duration(seconds: 60), (_) async {
Stream.periodic(Duration(seconds: 600), (_) async {
poll();
});
}

View file

@ -124,10 +124,6 @@ class _ProgressWidgetState extends State<ProgressWidget> {
),
],
),
if (kDebugMode)
Text(
'${(results.where((r) => r != Result.not_visited).length)}/${results.length}',
)
],
),
),

View file

@ -60,7 +60,8 @@ class _TestPageState extends State<TestPage> {
print('AUDIO $_lastAudioQuestionId ${question.id}');
_lastAudioQuestionId = question.id;
try {
if (globalSharedPreferences.getBool('auto_play_sound_tests') != false) {
if (globalSharedPreferences.getBool('auto_play_sound_tests') !=
false) {
AudioPlayer.playAudio((question as dynamic).audio);
}
} catch (e, s) {}
@ -156,39 +157,7 @@ class _TestPageState extends State<TestPage> {
TestQuestionType.undefined =>
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),
),
},
),
),
],

View file

@ -4,6 +4,7 @@ import 'dart:math' as m;
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:mnemo_cards/features/packs/images_holder.dart';
import 'package:mnemo_cards/theme/themes.dart';
@ -95,8 +96,8 @@ class TestImage {
if (data.length > 50) {
Uint8List? bytes;
try {
bytes = base64Decode(data);
return MemoryImage(bytes);
bytes = await compute(base64Decode, data);
return MemoryImage(bytes!);
} catch (e) {
log('Cant decode base64 test image');
return null;

View file

@ -7,12 +7,15 @@ import 'package:auto_route/auto_route.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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:mnemo_cards/di/locator.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/features/packs/pack_manager.dart';
import 'package:mnemo_cards/theme/themes.dart';
@ -30,6 +33,7 @@ import 'managers/repository/repository.dart';
final scaffoldKey = GlobalKey<ScaffoldState>();
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
late final SharedPreferences globalSharedPreferences;
late final FlutterSecureStorage secureStorage;
final appRouter = AppRouter(ADMIN_BUILD);
@ -45,7 +49,7 @@ void main() async {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
androidDeviceInfo = await DeviceInfoPlugin().androidInfo;
androidDeviceInfo = kIsWeb ? null : await DeviceInfoPlugin().androidInfo;
print('deviceInfo ${stopwatch.elapsedMilliseconds}');
final isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
print('No trunst ${stopwatch.elapsedMilliseconds}');
@ -65,7 +69,13 @@ void main() async {
};
try {
secureStorage = FlutterSecureStorage(
aOptions: const AndroidOptions(
encryptedSharedPreferences: true,
),
);
globalSharedPreferences = await SharedPreferences.getInstance();
await _fillSp();
print('global sp ${stopwatch.elapsedMilliseconds}');
await setInjections();
@ -81,32 +91,38 @@ void main() async {
await locator.previewPackPoller.init();
await locator.packUpdater.init();
print('initing complete ${stopwatch.elapsedMilliseconds}');
await MobileAds.initialize();
if (!kIsWeb) await MobileAds.initialize();
print('mobile ads ${stopwatch.elapsedMilliseconds}');
print('runApp ${stopwatch.elapsedMilliseconds}');
runApp(const MyApp());
} on Object catch (e, s) {
print('err: ${e.toString()}');
print('stacktrace: ${s.toString()}');
log(e.toString(), stackTrace: s);
exit(0);
}
}
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');
if (soundOn == null) {
globalSharedPreferences.setBool('sound_on', true);
await globalSharedPreferences.setBool('sound_on', true);
}
final soundSpeed = globalSharedPreferences.getDouble('sound_speed');
if (soundSpeed == null) {
globalSharedPreferences.setDouble('sound_speed', 1.0);
await globalSharedPreferences.setDouble('sound_speed', 1.0);
}
final autoPlay = globalSharedPreferences.getBool('auto_play_sound_tests');
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');
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,
routerConfig: appRouter.config(),
scaffoldMessengerKey: scaffoldMessengerKey,
debugShowCheckedModeBanner: false,
),
);
}

View 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(),
);
}

View file

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
@ -8,6 +9,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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:http_certificate_pinning/http_certificate_pinning.dart';
import 'package:flutter/foundation.dart';
@ -24,18 +26,11 @@ class DioProvider {
Dio get dio => _dio;
Future<DioProvider> init() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appVersion = packageInfo.version;
PackageInfo appPackageInfo = await PackageInfo.fromPlatform();
String appVersion = appPackageInfo.version;
_dio = Dio()
..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: [
// '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);
}),
InterceptorsWrapper(onRequest: (request, handler) {
request.headers = {...request.headers, 'app_version': appVersion};
InterceptorsWrapper(onRequest: (request, handler) async {
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);
})
]);
// final sslCert = await rootBundle.load('assets/ca.crt');
// final clientCert = await rootBundle.load('assets/client.crt');
// 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());
if (!kIsWeb) {
final securityContext = SecurityContext(withTrustedRoots: true);
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
HttpClient httpClient = HttpClient(context: securityContext)
..badCertificateCallback =
(X509Certificate cert, String host, int port) {
print(cert.issuer);
print('$host $port');
final now = DateTime.now();
final valid =
cert.startValidity.isBefore(now) && cert.endValidity.isAfter(now);
return valid &&
const DeepCollectionEquality().equals(
cert.sha1,
_cert,
);
return true;
};
return httpClient;
};
// securityContext.setTrustedCertificatesBytes(Certificates.crt);
// securityContext.usePrivateKeyBytes(Certificates.key);
_dio.httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () {
HttpClient httpClient = HttpClient(context: securityContext)
..badCertificateCallback =
(X509Certificate cert, String host, int port) {
print(cert.issuer);
print('$host $port');
final now = DateTime.now();
final valid = cert.startValidity.isBefore(now) &&
cert.endValidity.isAfter(now);
return valid &&
const DeepCollectionEquality().equals(
cert.sha1,
_cert,
);
};
return httpClient;
},
);
}
return this;
}

View file

@ -61,7 +61,7 @@ class HttpRepository extends Repository with Api {
void setAuthToken(String? authToken) {
_dio.interceptors.add(
InterceptorsWrapper(onRequest: (options, handler) {
options.headers[HttpHeaders.authorizationHeader] = authToken;
options.headers[AppHeaders.userToken] = authToken;
handler.next(options);
}),
);

View file

@ -38,6 +38,8 @@ class UserManager {
bool get hasUser => userStateHolder.user != null;
int? get userId => userStateHolder.user?.id;
Future<void> login() async {
if (await _googleSignIn.isSignedIn()) {
await _googleSignIn.signOut();
@ -61,17 +63,21 @@ class UserManager {
) async {
final (user, token) =
await _repository.createOrGetUser(externalId, idType, name);
_setAuthToken(token);
await _setAuthToken(token);
userStateHolder.setUser(user);
}
void _setAuthToken(String authToken) async {
Future<void> _setAuthToken(String authToken) async {
_repository.setAuthToken(authToken);
await _sharedPreferences!.setString('authToken', authToken);
await secureStorage.write(key: 'authToken', value: authToken);
}
void _clearAuthToken() async {
await _sharedPreferences!.remove('authToken');
Future<void> _clearAuthToken() async {
await secureStorage.delete(key: 'authToken');
}
Future<String?> getAuthToken() async {
return secureStorage.read(key: 'authToken');
}
Future<PromoCodeDto> applyPromocode(String code) async {
@ -80,9 +86,9 @@ class UserManager {
}
Future<UserDto?> updateUser() async {
String? authToken = _sharedPreferences!.getString('authToken');
String? authToken = await getAuthToken();
if (authToken != null) {
_setAuthToken(authToken);
await _setAuthToken(authToken);
try {
final fetchedUser = await _repository.getUser();
userStateHolder.setUser(fetchedUser!);
@ -117,40 +123,44 @@ class UserManager {
.listen((account) async {
if (account != null) {
final name = account.displayName;
final token =
final externalToken =
await account.authentication.then((value) => value.idToken);
Analytics.login(ExternalIdType.google);
await _createUser(token!, ExternalIdType.google, name);
await _createUser(externalToken!, ExternalIdType.google, name);
}
}))
..add(userStateHolder.asNullableStream
.distinct((a, b) => a?.id == b?.id)
.listen((user) async {
try {
if (user == null) {
AppRouter.openAuthOrProfile();
} else {
..add(
userStateHolder.asNullableStream
.distinct((a, b) => a?.id == b?.id)
// don't want to clear cache on login
.skip(1)
.listen(
(user) async {
try {
AppRouter.closeAuthPage();
await locator.previewPackPoller.poll();
await locator.packManager.clearCache();
await locator.packUpdater.updateAvailablePacks();
} catch (err) {
log(err.toString());
if (user == null) {
AppRouter.openAuthOrProfile();
} else {
try {
AppRouter.closeAuthPage();
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 {

View file

@ -41,7 +41,7 @@ class AuthPage extends StatelessWidget {
fit: BoxFit.fitWidth,
),
),
if (SHOW_ADMIN)
if (ADMIN_BUILD)
SharedPrefButton<bool>(
builder: (v, _) => (v ?? false) ? Text('PROD') : Text('TEST'),
spKey: 'env',

View file

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

View file

@ -2,6 +2,7 @@ import 'dart:math';
import 'package:collection/collection.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:mnemo_cards/main.dart';
@ -270,6 +271,9 @@ class _AdTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (locator.userManager.userStateHolder.user?.subscription == true)
return SizedBox.shrink();
if (kIsWeb) return SizedBox.shrink();
return LayoutBuilder(builder: (context, constraints) {
final banner = YandexAds.createBanner(
BoxConstraints.tight(Size(constraints.maxWidth - 16, 80 - 8)));

View file

@ -78,24 +78,32 @@ class _PacksMenuWidgetState extends State<PacksMenuWidget> {
backgroundColor: Colors.white,
color: snapshot.data?.firstOrNull?.color?.asColor ??
Colors.black,
child: ListView(
physics: BouncingScrollPhysics(
decelerationRate: ScrollDecelerationRate.fast,
parent: AlwaysScrollableScrollPhysics()),
scrollDirection: Axis.vertical,
children: [
if (snapshot.data == null)
...[1, 2, 3, 4].map(
(e) => _PackButton4Shimmer(
constraints.maxWidth,
cardHeight,
child: (!snapshot.hasData)
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'images/cerdo.jpg',
width: 100,
),
),
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) {
if (pack.imageBase64 != null) {
try {
final bytes = base64Decode(pack.imageBase64!);
final bytes = await compute(base64Decode, pack.imageBase64!);
final image = MemoryImage(bytes);
locator.imagesHolder.setImage('pack_${pack.id}', image);
await precacheImage(image, context);
@ -165,18 +173,11 @@ class _PackButton4 extends StatelessWidget {
children: [
Container(
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
? null
: DecorationImage(
image: locator.imagesHolder.get('pack_${pack.id}') ??
MemoryImage(
base64Decode(pack.imageBase64!),
),
MemoryImage(base64Decode(pack.imageBase64!)),
),
),
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,
)
],
),
),
),
);
}
}

View file

@ -494,6 +494,54 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct dev"
description: flutter
@ -732,10 +780,10 @@ packages:
dependency: transitive
description:
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.7.1"
version: "0.6.7"
json_annotation:
dependency: "direct main"
description:

View file

@ -1,32 +1,12 @@
name: mnemo_cards
description: Mnemo
# The following line prevents the package from being accidentally published to
# 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
publish_to: 'none'
# The following defines the version and build number for your application.
# 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
version: 1.0.1
environment:
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:
flutter:
sdk: flutter
@ -35,11 +15,12 @@ dependencies:
path: ../mnemo_cards_common
get_it:
shared_preferences:
shared_preferences: ^2.2.3
flutter_secure_storage: ^9.2.2
rxdart:
firebase_core: ^2.30.1
# firebase_crashlytics:
# firebase_storage:
# firebase_crashlytics:
# firebase_storage:
cloud_firestore: ^4.17.2
freezed:
json_serializable:
@ -78,6 +59,8 @@ dependencies:
http_certificate_pinning: ^2.1.3
url_launcher: ^6.2.6
# requests_inspector: ^4.0.3
dev_dependencies:
flutter_test:
@ -103,11 +86,11 @@ flutter:
flutter_launcher_icons:
android: "launcher_icon"
ios: true
image_path: "images/cerdo.jpg"
image_path: "images/icon.png"
min_sdk_android: 21 # android min sdk min:16, default 21
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"
#background_image: "assets/background.png"
@ -141,16 +124,16 @@ flutter_native_splash:
# Please visit https://developer.android.com/guide/topics/ui/splash-screen
# Following are specific parameters for Android 12+.
android_12:
# The image parameter sets the splash screen icon image. If this parameter is not specified,
# 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.
# App icon with an icon background: This should be 960×960 pixels, and fit within a circle
# 640 pixels in diameter.
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
# 768 pixels in diameter.
# The image parameter sets the splash screen icon image. If this parameter is not specified,
# 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.
# App icon with an icon background: This should be 960×960 pixels, and fit within a circle
# 640 pixels in diameter.
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
# 768 pixels in diameter.
image: images/cerdo_big.jpg
# Splash screen background color.
# Splash screen background color.
color: "#ffffff"
# App icon background color.

0
v Normal file
View file