Skip to content

Commit

Permalink
Merge pull request #86 from KomodoPlatform/fix-file-sharing-export
Browse files Browse the repository at this point in the history
Fix file sharing export & log refactoring
  • Loading branch information
ca333 authored Dec 18, 2023
2 parents e32aff7 + be1f64a commit 8dcbbcb
Show file tree
Hide file tree
Showing 15 changed files with 423 additions and 192 deletions.
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

0 comments on commit 8dcbbcb

Please sign in to comment.