mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-23 01:06:21 +01:00
Capture logs from settings for bug report
This commit is contained in:
parent
858455206c
commit
620c840ccc
11 changed files with 210 additions and 15 deletions
|
@ -48,6 +48,13 @@ class NotificationChannelHandler(activity: Activity)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
|
} else if (call.method == "notifyLogSaveSuccessful") {
|
||||||
|
try {
|
||||||
|
notifyLogSaveSuccessful(call.argument<String>("fileUri")!!,
|
||||||
|
result)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
result.error("systemException", e.toString(), null)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
}
|
}
|
||||||
|
@ -130,6 +137,44 @@ class NotificationChannelHandler(activity: Activity)
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun notifyLogSaveSuccessful(fileUri: String,
|
||||||
|
result: MethodChannel.Result) {
|
||||||
|
val uri = Uri.parse(fileUri)
|
||||||
|
val mimeType = "text/plain"
|
||||||
|
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.baseline_download_white_18)
|
||||||
|
.setWhen(System.currentTimeMillis())
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setSound(RingtoneManager.getDefaultUri(
|
||||||
|
RingtoneManager.TYPE_NOTIFICATION))
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setLocalOnly(true)
|
||||||
|
.setTicker(_context.getString(
|
||||||
|
R.string.log_save_successful_notification_title))
|
||||||
|
.setContentTitle(_context.getString(
|
||||||
|
R.string.log_save_successful_notification_title))
|
||||||
|
.setContentText(_context.getString(
|
||||||
|
R.string.log_save_successful_notification_text))
|
||||||
|
|
||||||
|
val openIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
setDataAndType(uri, mimeType)
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
val openPendingIntent = PendingIntent.getActivity(_context, 0,
|
||||||
|
openIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
builder.setContentIntent(openPendingIntent)
|
||||||
|
|
||||||
|
// can't add the share action here because android will share the URI as
|
||||||
|
// plain text instead of treating it as a text file...
|
||||||
|
|
||||||
|
with(NotificationManagerCompat.from(_context)) {
|
||||||
|
notify(DOWNLOAD_NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadNotificationImage(fileUri: Uri): Bitmap? {
|
private fun loadNotificationImage(fileUri: Uri): Bitmap? {
|
||||||
try {
|
try {
|
||||||
val resolver = _context.applicationContext.contentResolver
|
val resolver = _context.applicationContext.contentResolver
|
||||||
|
|
|
@ -9,4 +9,6 @@
|
||||||
<string name="download_successful_notification_action_share">SHARE</string>
|
<string name="download_successful_notification_action_share">SHARE</string>
|
||||||
<string name="download_successful_notification_action_share_chooser">Share with:</string>
|
<string name="download_successful_notification_action_share_chooser">Share with:</string>
|
||||||
<string name="download_multiple_successful_notification_title">Downloaded %1$s items successfully</string>
|
<string name="download_multiple_successful_notification_title">Downloaded %1$s items successfully</string>
|
||||||
|
<string name="log_save_successful_notification_title">Logs saved successfully</string>
|
||||||
|
<string name="log_save_successful_notification_text">Tap to view your saved logs</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -71,21 +71,6 @@ class ListPersonBlocFailure extends ListPersonBlocState {
|
||||||
class ListPersonBloc extends Bloc<ListPersonBlocEvent, ListPersonBlocState> {
|
class ListPersonBloc extends Bloc<ListPersonBlocEvent, ListPersonBlocState> {
|
||||||
ListPersonBloc() : super(ListPersonBlocInit());
|
ListPersonBloc() : super(ListPersonBlocInit());
|
||||||
|
|
||||||
static ListPersonBloc of(Account account) {
|
|
||||||
final id = "${account.scheme}://${account.username}@${account.address}";
|
|
||||||
try {
|
|
||||||
_log.fine("[of] Resolving bloc for '$id'");
|
|
||||||
return KiwiContainer().resolve<ListPersonBloc>("ListPersonBloc($id)");
|
|
||||||
} catch (_) {
|
|
||||||
// no created instance for this account, make a new one
|
|
||||||
_log.info("[of] New bloc instance for account: $account");
|
|
||||||
final bloc = ListPersonBloc();
|
|
||||||
KiwiContainer()
|
|
||||||
.registerInstance<ListPersonBloc>(bloc, name: "ListPersonBloc($id)");
|
|
||||||
return bloc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
mapEventToState(ListPersonBlocEvent event) async* {
|
mapEventToState(ListPersonBlocEvent event) async* {
|
||||||
_log.info("[mapEventToState] $event");
|
_log.info("[mapEventToState] $event");
|
||||||
|
|
|
@ -1,3 +1,45 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:nc_photos/mobile/platform.dart'
|
||||||
|
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
||||||
|
|
||||||
|
class LogCapturer {
|
||||||
|
factory LogCapturer() {
|
||||||
|
if (_inst == null) {
|
||||||
|
_inst = LogCapturer._();
|
||||||
|
}
|
||||||
|
return _inst!;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogCapturer._();
|
||||||
|
|
||||||
|
/// Start capturing logs
|
||||||
|
void start() {
|
||||||
|
_isEnable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop capturing and save the captured logs
|
||||||
|
Future<dynamic> stop() {
|
||||||
|
_isEnable = false;
|
||||||
|
final saver = platform.FileSaver();
|
||||||
|
final content = Utf8Encoder().convert(_logs.join("\n"));
|
||||||
|
_logs.clear();
|
||||||
|
return saver.saveFile("nc-photos.log", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onLog(String log) {
|
||||||
|
if (_isEnable) {
|
||||||
|
_logs.add(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isEnable => _isEnable;
|
||||||
|
|
||||||
|
final _logs = <String>[];
|
||||||
|
bool _isEnable = false;
|
||||||
|
|
||||||
|
static LogCapturer? _inst;
|
||||||
|
}
|
||||||
|
|
||||||
const bool shouldLogFileName = kDebugMode;
|
const bool shouldLogFileName = kDebugMode;
|
||||||
|
|
|
@ -361,6 +361,11 @@
|
||||||
"@settingsBugReportTitle": {
|
"@settingsBugReportTitle": {
|
||||||
"description": "Report issue"
|
"description": "Report issue"
|
||||||
},
|
},
|
||||||
|
"settingsCaptureLogsTitle": "Capture logs",
|
||||||
|
"@settingsCaptureLogsTitle": {
|
||||||
|
"description": "Capture app logs for bug report"
|
||||||
|
},
|
||||||
|
"settingsCaptureLogsDescription": "Help developer to diagnose bugs",
|
||||||
"settingsTranslatorTitle": "Translator",
|
"settingsTranslatorTitle": "Translator",
|
||||||
"@settingsTranslatorTitle": {
|
"@settingsTranslatorTitle": {
|
||||||
"description": "Title of the translator item"
|
"description": "Title of the translator item"
|
||||||
|
@ -381,6 +386,14 @@
|
||||||
"@exifSupportConfirmationDialogTitle": {
|
"@exifSupportConfirmationDialogTitle": {
|
||||||
"description": "Title of the dialog to confirm enabling exif support"
|
"description": "Title of the dialog to confirm enabling exif support"
|
||||||
},
|
},
|
||||||
|
"captureLogDetails": "To take logs for a bug report:\n\n1. Enable this setting\n2. Reproduce the issue\n3. Disable this setting\n4. Look for nc-photos.log in the download folder\n\n*If the issue causes the app to crash, no logs could be captured. In such case, please contact the developer for further instructions",
|
||||||
|
"@captureLogDetails": {
|
||||||
|
"description": "Detailed description on capturing logs"
|
||||||
|
},
|
||||||
|
"captureLogSuccessNotification": "Logs saved successfully",
|
||||||
|
"@captureLogSuccessNotification": {
|
||||||
|
"description": "Captured logs are successfully saved to the download directory"
|
||||||
|
},
|
||||||
"doneButtonLabel": "DONE",
|
"doneButtonLabel": "DONE",
|
||||||
"@doneButtonLabel": {
|
"@doneButtonLabel": {
|
||||||
"description": "Label of the done button"
|
"description": "Label of the done button"
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
"settingsUseBlackInDarkThemeTitle",
|
"settingsUseBlackInDarkThemeTitle",
|
||||||
"settingsUseBlackInDarkThemeTrueDescription",
|
"settingsUseBlackInDarkThemeTrueDescription",
|
||||||
"settingsUseBlackInDarkThemeFalseDescription",
|
"settingsUseBlackInDarkThemeFalseDescription",
|
||||||
|
"settingsCaptureLogsTitle",
|
||||||
|
"settingsCaptureLogsDescription",
|
||||||
|
"captureLogDetails",
|
||||||
|
"captureLogSuccessNotification",
|
||||||
"sortOptionAlbumNameLabel",
|
"sortOptionAlbumNameLabel",
|
||||||
"sortOptionAlbumNameDescendingLabel",
|
"sortOptionAlbumNameDescendingLabel",
|
||||||
"listEmptyText",
|
"listEmptyText",
|
||||||
|
@ -51,6 +55,13 @@
|
||||||
"unmuteTooltip"
|
"unmuteTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"es": [
|
||||||
|
"settingsCaptureLogsTitle",
|
||||||
|
"settingsCaptureLogsDescription",
|
||||||
|
"captureLogDetails",
|
||||||
|
"captureLogSuccessNotification"
|
||||||
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
"collectionsTooltip",
|
"collectionsTooltip",
|
||||||
"settingsViewerTitle",
|
"settingsViewerTitle",
|
||||||
|
@ -67,6 +78,10 @@
|
||||||
"settingsUseBlackInDarkThemeTitle",
|
"settingsUseBlackInDarkThemeTitle",
|
||||||
"settingsUseBlackInDarkThemeTrueDescription",
|
"settingsUseBlackInDarkThemeTrueDescription",
|
||||||
"settingsUseBlackInDarkThemeFalseDescription",
|
"settingsUseBlackInDarkThemeFalseDescription",
|
||||||
|
"settingsCaptureLogsTitle",
|
||||||
|
"settingsCaptureLogsDescription",
|
||||||
|
"captureLogDetails",
|
||||||
|
"captureLogSuccessNotification",
|
||||||
"sortOptionAlbumNameLabel",
|
"sortOptionAlbumNameLabel",
|
||||||
"sortOptionAlbumNameDescendingLabel",
|
"sortOptionAlbumNameDescendingLabel",
|
||||||
"helpTooltip",
|
"helpTooltip",
|
||||||
|
@ -84,6 +99,10 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
"settingsCaptureLogsTitle",
|
||||||
|
"settingsCaptureLogsDescription",
|
||||||
|
"captureLogDetails",
|
||||||
|
"captureLogSuccessNotification",
|
||||||
"sortOptionAlbumNameLabel",
|
"sortOptionAlbumNameLabel",
|
||||||
"sortOptionAlbumNameDescendingLabel"
|
"sortOptionAlbumNameDescendingLabel"
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/android/android_info.dart';
|
import 'package:nc_photos/mobile/android/android_info.dart';
|
||||||
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
||||||
|
@ -69,6 +70,7 @@ void _initLog() {
|
||||||
msg = "\x1B[${color}m$msg\x1B[0m";
|
msg = "\x1B[${color}m$msg\x1B[0m";
|
||||||
}
|
}
|
||||||
debugPrint(msg);
|
debugPrint(msg);
|
||||||
|
LogCapturer().onLog(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@ class Notification {
|
||||||
"mimeTypes": mimeTypes,
|
"mimeTypes": mimeTypes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static Future<void> notifyLogSaveSuccessful(String fileUri) =>
|
||||||
|
_channel.invokeMethod("notifyLogSaveSuccessful", <String, dynamic>{
|
||||||
|
"fileUri": fileUri,
|
||||||
|
});
|
||||||
|
|
||||||
static const _channel =
|
static const _channel =
|
||||||
const MethodChannel("com.nkming.nc_photos/notification");
|
const MethodChannel("com.nkming.nc_photos/notification");
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,3 +13,15 @@ class AndroidItemDownloadSuccessfulNotification
|
||||||
final List<String> fileUris;
|
final List<String> fileUris;
|
||||||
final List<String?> mimeTypes;
|
final List<String?> mimeTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AndroidLogSaveSuccessfulNotification
|
||||||
|
extends itf.LogSaveSuccessfulNotification {
|
||||||
|
AndroidLogSaveSuccessfulNotification(this.fileUri);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> notify() {
|
||||||
|
return Notification.notifyLogSaveSuccessful(fileUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String fileUri;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
abstract class ItemDownloadSuccessfulNotification {
|
abstract class ItemDownloadSuccessfulNotification {
|
||||||
Future<void> notify();
|
Future<void> notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class LogSaveSuccessfulNotification {
|
||||||
|
Future<void> notify();
|
||||||
|
}
|
||||||
|
|
|
@ -4,11 +4,13 @@ import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart' as language_util;
|
import 'package:nc_photos/language_util.dart' as language_util;
|
||||||
import 'package:nc_photos/metadata_task_manager.dart';
|
import 'package:nc_photos/metadata_task_manager.dart';
|
||||||
import 'package:nc_photos/mobile/android/android_info.dart';
|
import 'package:nc_photos/mobile/android/android_info.dart';
|
||||||
|
import 'package:nc_photos/mobile/notification.dart';
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
@ -122,6 +124,12 @@ class _SettingsState extends State<Settings> {
|
||||||
launch(_bugReportUrl);
|
launch(_bugReportUrl);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(L10n.global().settingsCaptureLogsTitle),
|
||||||
|
subtitle: Text(L10n.global().settingsCaptureLogsDescription),
|
||||||
|
value: LogCapturer().isEnable,
|
||||||
|
onChanged: (value) => _onCaptureLogChanged(context, value),
|
||||||
|
),
|
||||||
if (translator.isNotEmpty)
|
if (translator.isNotEmpty)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.global().settingsTranslatorTitle),
|
title: Text(L10n.global().settingsTranslatorTitle),
|
||||||
|
@ -251,6 +259,64 @@ class _SettingsState extends State<Settings> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onCaptureLogChanged(BuildContext context, bool value) async {
|
||||||
|
if (value) {
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AppTheme(
|
||||||
|
child: AlertDialog(
|
||||||
|
content: Text(L10n.global().captureLogDetails),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
child: Text(L10n.global().enableButtonLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result == true) {
|
||||||
|
setState(() {
|
||||||
|
LogCapturer().start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (LogCapturer().isEnable) {
|
||||||
|
setState(() {
|
||||||
|
LogCapturer().stop().then((result) {
|
||||||
|
_onLogSaveSuccessful(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onLogSaveSuccessful(dynamic result) {
|
||||||
|
var notif;
|
||||||
|
if (platform_k.isAndroid) {
|
||||||
|
notif = AndroidLogSaveSuccessfulNotification(result);
|
||||||
|
}
|
||||||
|
if (notif != null) {
|
||||||
|
try {
|
||||||
|
notif.notify();
|
||||||
|
return;
|
||||||
|
} catch (e, stacktrace) {
|
||||||
|
_log.shout(
|
||||||
|
"[_onLogSaveSuccessful] Failed showing platform notification",
|
||||||
|
e,
|
||||||
|
stacktrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(L10n.global().downloadSuccessNotification),
|
||||||
|
duration: k.snackBarDurationShort,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _setExifSupport(bool value) async {
|
Future<void> _setExifSupport(bool value) async {
|
||||||
final oldValue = _isEnableExif;
|
final oldValue = _isEnableExif;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
Loading…
Reference in a new issue