Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix file sharing export & log refactoring #86

Merged
merged 5 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ PODS:
- SDWebImage (5.18.3):
- SDWebImage/Core (= 5.18.3)
- SDWebImage/Core (5.18.3)
- share (0.0.1):
- share_plus (0.0.1):
- Flutter
- shared_preferences_ios (0.0.1):
- Flutter
Expand All @@ -147,7 +147,7 @@ DEPENDENCIES:
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- share (from `.symlinks/plugins/share/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
Expand Down Expand Up @@ -201,8 +201,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios"
share:
:path: ".symlinks/plugins/share/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
sqflite:
Expand Down Expand Up @@ -242,7 +242,7 @@ SPEC CHECKSUMS:
Protobuf: 351e9022fe13a6e2af00e9aefc22077cb88520f8
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 96e0c18ef14010b7485210e92fac888587ebb958
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
Expand Down
198 changes: 121 additions & 77 deletions lib/app_config/coins_updater.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:komodo_dex/utils/log.dart';
import 'package:path_provider/path_provider.dart';
import 'package:komodo_dex/utils/utils.dart';

/// Provides methods for fetching coin data either from local assets or a remote Git repository.
///
Expand Down Expand Up @@ -49,109 +50,152 @@ class CoinUpdater {
String _cachedConfig;
String _cachedCoins;

Future<String> _fetchAsset(String path) async {
return await rootBundle.loadString(path);
}
Future<String> _fetchAsset(String path) =>
rootBundle.loadString(path, cache: false);

Future<File> _getLocalFile(String filename) async {
final directory = await getApplicationDocumentsDirectory();
return File('${directory.path}/$filename');
final directory = await applicationDocumentsDirectory;
return File('${directory.path}/config_updates/$filename');
}

Future<String> _fetchOrCache(
String localPath,
String remoteUrl,
String cacheName,
String cacheProperty,
) async {
try {
if (cacheProperty != null) {
return cacheProperty;
}
Future<String> _fetchCoinFileOrAsset(UpdateCacheParams params) async {
File cacheFile;
String property;

File cacheFile = await _getLocalFile(cacheName);
try {
try {
cacheFile = await _getLocalFile(params.cacheFileName);

final cacheFileExists = await cacheFile.exists();
final maybeCacheValue = await compute<String, String>(
_tryReadValidJsonFile,
cacheFile.path,
);

if (isUpdateEnabled) {
scheduleMicrotask(
() => _updateCacheInBackground(remoteUrl, cacheFile),
property = maybeCacheValue ?? property;
} catch (e) {
Log(
'CoinUpdater',
'Error reading coin config cache file: ${e.toString()}',
);
}

if (cacheFileExists) {
cacheProperty = await cacheFile.readAsString();
property ??= await _fetchAsset(params.localPath);

return cacheProperty;
} else {
String localData = await _fetchAsset(localPath);
cacheProperty = localData;
return localData;
}
return property;
} catch (e) {
// If there's an error, first try to return the cached value,
// if that's null too, then fall back to the local asset.
if (cacheProperty != null) {
return cacheProperty;
} else {
return await _fetchAsset(localPath);
Log('CoinUpdater', 'Error fetching or caching ${params.cacheKey}: $e');
rethrow;
} finally {
if (isUpdateEnabled) {
_startUpdateCacheInBackground(params.remoteUrl, cacheFile);
}
}
}

void _updateCacheInBackground(String remoteUrl, File cacheFile) async {
final ReceivePort receivePort = ReceivePort();

void _startUpdateCacheInBackground(String remoteUrl, File cacheFile) async {
try {
await Isolate.spawn(
_isolateEntry,
[remoteUrl, cacheFile.path],
onExit: receivePort.sendPort,
errorsAreFatal: false,
Log('CoinUpdater', 'Updating coins in background...');
await compute<Map<String, dynamic>, void>(
_updateFileFromServer,
<String, dynamic>{
'remoteUrl': remoteUrl,
'filePath': cacheFile.path,
},
);
receivePort.listen((data) {
// Close the receive port when the isolate is done
receivePort.close();

Log(
'CoinUpdater',
'Coin updater updated coins to latest commit on branch '
'$coinsRepoBranch from $coinsRepoUrl. \n $remoteUrl',
);
});
Log(
'CoinUpdater',
'Coin updater updated coins to latest commit on branch $coinsRepoBranch'
' from $coinsRepoUrl. Changes will take effect on next app launch.',
);
} catch (e) {
Log('CoinUpdater', 'Error updating coins: $e');
}
}

static void _isolateEntry(List<String> data) async {
final String remoteUrl = data[0];
final String filePath = data[1];

final response = await http.get(Uri.parse(remoteUrl));
if (response.statusCode == 200) {
final file = File(filePath);
file.writeAsString(response.body);
Log('CoinUpdater', 'Error updating coins in background: $e');
}
}

Future<String> getConfig() async {
_cachedConfig = await _fetchOrCache(
localAssetPathConfig,
remotePathConfig,
'coins_config_cache.json',
_cachedConfig,
return _cachedConfig ??= await _fetchCoinFileOrAsset(
UpdateCacheParams(
localPath: localAssetPathConfig,
remoteUrl: remotePathConfig,
cacheFileName: 'coins_config_cache.json',
cacheKey: 'config',
),
);
return _cachedConfig;
}

Future<String> getCoins() async {
_cachedCoins = await _fetchOrCache(
localAssetPathCoins,
remotePathCoins,
'coins_cache.json',
_cachedCoins,
return _cachedCoins ??= await _fetchCoinFileOrAsset(
UpdateCacheParams(
localPath: localAssetPathCoins,
remoteUrl: remotePathCoins,
cacheFileName: 'coins_cache.json',
cacheKey: 'coins',
),
);
return _cachedCoins;
}
}

class UpdateCacheParams {
final String localPath;
final String remoteUrl;
final String cacheFileName;
final String cacheKey;

UpdateCacheParams({
@required this.localPath,
@required this.remoteUrl,
@required this.cacheFileName,
@required this.cacheKey,
});
}

/// Isolate-safe method for returning the contents of a JSON file if it is valid
Future<String> _tryReadValidJsonFile(String path) async {
try {
final contents = await File(path).readAsString();

if (!_isJsonValid(contents)) return null;

return contents;
} catch (e) {
return null;
}
}

/// An isolate-safe method for checking if a string is valid JSON.
bool _isJsonValid(String json) {
try {
if (json?.isEmpty ?? true) return false;

jsonDecode(json);
return true;
} catch (e) {
return false;
}
}

/// Isolate-safe method for fetching and updating a JSON file from a
/// remote server.
Future<void> _updateFileFromServer(Map<String, dynamic> params) async {
final String remoteUrl = params['remoteUrl'];
final String filePath = params['filePath'];

try {
final response = await http.get(Uri.parse(remoteUrl));

if (response.statusCode != 200 || !_isJsonValid(response.body)) return;

final file = File(filePath);

if (!file.existsSync()) {
file.createSync(recursive: true);
}

file.writeAsStringSync(response.body, flush: true);
} catch (e) {
print('Error in isolate: $e');

rethrow;
}
}
54 changes: 45 additions & 9 deletions lib/screens/authentification/authenticate_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:komodo_dex/utils/log.dart';
import 'package:komodo_dex/widgets/repeated_tap_detector.dart';
import '../../blocs/authenticate_bloc.dart';
import '../../blocs/wallet_bloc.dart';
import '../../localizations.dart';
Expand Down Expand Up @@ -119,8 +121,9 @@ class BuildScreenAuthMultiWallets extends StatelessWidget {
),
),
Align(
alignment: Alignment.centerRight,
child: const SelectLanguageButton()),
alignment: Alignment.centerRight,
child: const SelectLanguageButton(),
),
SizedBox(height: 16),
IntrinsicHeight(
child: Row(
Expand Down Expand Up @@ -251,13 +254,7 @@ class _BuildScreenAuthState extends State<BuildScreenAuth> {
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 240,
width: 240,
child: Image.asset(Theme.of(context).brightness ==
Brightness.light
? 'assets/branding/mark_and_text_vertical_dark.png'
: 'assets/branding/mark_and_text_vertical_light.png')),
_FullAppLogo(),
],
),
Padding(
Expand Down Expand Up @@ -295,6 +292,45 @@ class _BuildScreenAuthState extends State<BuildScreenAuth> {
}
}

class _FullAppLogo extends StatelessWidget {
const _FullAppLogo({
Key key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return RepeatedTapDetector(
onRepeatedTap: () => _downloadLogs(context),
tapTriggerCount: 7,
child: SizedBox(
height: 240,
width: 240,
child: Image.asset(Theme.of(context).brightness == Brightness.light
? 'assets/branding/mark_and_text_vertical_dark.png'
: 'assets/branding/mark_and_text_vertical_light.png')),
);
}

/// If the user taps the branding logo 7 times in a row, the app will
/// download the logs and share them via the system share sheet. This is so
/// that users can download logs even if they can't access the settings page.
/// E.g. if the app crashes on login.
void _downloadLogs(BuildContext context) {
Log.downloadLogs().catchError((e) {
_showSnackbar(context, e.toString());
});
}

void _showSnackbar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 3),
),
);
}
}

class CreateWalletButton extends StatelessWidget {
const CreateWalletButton({Key key}) : super(key: key);

Expand Down
15 changes: 8 additions & 7 deletions lib/screens/dex/orders/swap/final_trade_success.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import 'dart:io';
import 'dart:ui' as ui;

import 'package:cross_file/cross_file.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:share_plus/share_plus.dart';

import '../../../../app_config/app_config.dart';
import '../../../../blocs/dialog_bloc.dart';
import '../../../../localizations.dart';
import '../../../../model/swap.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../dex/orders/swap/detail_swap.dart';
import '../../../dex/orders/swap/share_preview_overlay.dart';
import '../../../../utils/utils.dart';
import '../../../../widgets/swap_share_card.dart';
import 'package:share/share.dart';
import '../../../dex/orders/swap/detail_swap.dart';
import '../../../dex/orders/swap/share_preview_overlay.dart';

class FinalTradeSuccess extends StatefulWidget {
const FinalTradeSuccess({@required this.swap});
Expand Down Expand Up @@ -167,10 +169,9 @@ class _FinalTradeSuccessState extends State<FinalTradeSuccess>
' on my phone! You can try it too: https://komodoplatform.com\n'
'#blockchain #dex #atomicdex #komodoplatform #atomicswap';

await Share.shareFiles(
[imgFile.path],
await Share.shareXFiles(
[XFile(imgFile.path, mimeType: 'image/png')],
text: shareText,
mimeTypes: ['image/png'],
);

if (Platform.isIOS) {
Expand Down
Loading
Loading