about: bug reporting instructions

This commit is contained in:
Thibault Deckers 2021-09-07 12:12:35 +09:00
parent f16645dd34
commit cbba70d069
12 changed files with 295 additions and 33 deletions

View file

@ -14,4 +14,4 @@ __We collect anonymous data to improve the app.__ We use Google Firebase for Cra
## Links ## Links
[Sources](https://github.com/deckerst/aves) [Sources](https://github.com/deckerst/aves)
[License](https://github.com/deckerst/aves/blob/master/LICENSE) [License](https://github.com/deckerst/aves/blob/main/LICENSE)

View file

@ -363,8 +363,11 @@
"aboutPageTitle": "About", "aboutPageTitle": "About",
"@aboutPageTitle": {}, "@aboutPageTitle": {},
"aboutFlutter": "Flutter", "aboutLinkSources": "Sources",
"@aboutFlutter": {}, "@aboutLinkSources": {},
"aboutLinkLicense": "License",
"@aboutLinkLicense": {},
"aboutUpdate": "New Version Available", "aboutUpdate": "New Version Available",
"@aboutUpdate": {}, "@aboutUpdate": {},
"aboutUpdateLinks1": "A new version of Aves is available on", "aboutUpdateLinks1": "A new version of Aves is available on",
@ -377,12 +380,29 @@
"@aboutUpdateGitHub": {}, "@aboutUpdateGitHub": {},
"aboutUpdateGooglePlay": "Google Play", "aboutUpdateGooglePlay": "Google Play",
"@aboutUpdateGooglePlay": {}, "@aboutUpdateGooglePlay": {},
"aboutBug": "Bug Report",
"@aboutBug": {},
"aboutBugSaveLogInstruction": "Save app logs to a file",
"@aboutBugSaveLogInstruction": {},
"aboutBugSaveLogButton": "Save",
"@aboutBugSaveLogButton": {},
"aboutBugCopyInfoInstruction": "Copy system information",
"@aboutBugCopyInfoInstruction": {},
"aboutBugCopyInfoButton": "Copy",
"@aboutBugCopyInfoButton": {},
"aboutBugReportInstruction": "Report on GitHub with the logs and system information",
"@aboutBugReportInstruction": {},
"aboutBugReportButton": "Report",
"@aboutBugReportButton": {},
"aboutCredits": "Credits", "aboutCredits": "Credits",
"@aboutCredits": {}, "@aboutCredits": {},
"aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from", "aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from",
"@aboutCreditsWorldAtlas1": {}, "@aboutCreditsWorldAtlas1": {},
"aboutCreditsWorldAtlas2": "under ISC License.", "aboutCreditsWorldAtlas2": "under ISC License.",
"@aboutCreditsWorldAtlas2": {}, "@aboutCreditsWorldAtlas2": {},
"aboutLicenses": "Open-Source Licenses", "aboutLicenses": "Open-Source Licenses",
"@aboutLicenses": {}, "@aboutLicenses": {},
"aboutLicensesBanner": "This app uses the following open-source packages and libraries.", "aboutLicensesBanner": "This app uses the following open-source packages and libraries.",
@ -714,7 +734,7 @@
"settingsSectionPrivacy": "Privacy", "settingsSectionPrivacy": "Privacy",
"@settingsSectionPrivacy": {}, "@settingsSectionPrivacy": {},
"settingsEnableCrashReport": "Allow anonymous crash reporting", "settingsEnableCrashReport": "Allow anonymous error reporting",
"@settingsEnableCrashReport": {}, "@settingsEnableCrashReport": {},
"settingsSaveSearchHistory": "Save search history", "settingsSaveSearchHistory": "Save search history",
"@settingsSaveSearchHistory": {}, "@settingsSaveSearchHistory": {},

View file

@ -170,16 +170,28 @@
"menuActionStats": "통계", "menuActionStats": "통계",
"aboutPageTitle": "앱 정보", "aboutPageTitle": "앱 정보",
"aboutFlutter": "플러터", "aboutLinkSources": "소스 코드",
"aboutLinkLicense": "라이선스",
"aboutUpdate": "업데이트 사용 가능", "aboutUpdate": "업데이트 사용 가능",
"aboutUpdateLinks1": "앱의 최신 버전을", "aboutUpdateLinks1": "앱의 최신 버전을",
"aboutUpdateLinks2": "와", "aboutUpdateLinks2": "와",
"aboutUpdateLinks3": "에서 다운로드 사용 가능합니다.", "aboutUpdateLinks3": "에서 다운로드 사용 가능합니다.",
"aboutUpdateGitHub": "깃허브", "aboutUpdateGitHub": "깃허브",
"aboutUpdateGooglePlay": "구글 플레이", "aboutUpdateGooglePlay": "구글 플레이",
"aboutBug": "버그 보고",
"aboutBugSaveLogInstruction": "앱 로그를 파일에 저장하기",
"aboutBugSaveLogButton": "저장",
"aboutBugCopyInfoInstruction": "시스템 정보를 복사하기",
"aboutBugCopyInfoButton": "복사",
"aboutBugReportInstruction": "로그와 시스템 정보를 첨부하여 깃허브에서 이슈를 제툴하기",
"aboutBugReportButton": "제출",
"aboutCredits": "크레딧", "aboutCredits": "크레딧",
"aboutCreditsWorldAtlas1": "이 앱은", "aboutCreditsWorldAtlas1": "이 앱은",
"aboutCreditsWorldAtlas2": "의 TopoJSON 파일(ISC 라이선스)을 이용합니다.", "aboutCreditsWorldAtlas2": "의 TopoJSON 파일(ISC 라이선스)을 이용합니다.",
"aboutLicenses": "오픈 소스 라이선스", "aboutLicenses": "오픈 소스 라이선스",
"aboutLicensesBanner": "이 앱은 다음의 오픈 소스 패키지와 라이브러리를 이용합니다.", "aboutLicensesBanner": "이 앱은 다음의 오픈 소스 패키지와 라이브러리를 이용합니다.",
"aboutLicensesAndroidLibraries": "안드로이드 라이브러리", "aboutLicensesAndroidLibraries": "안드로이드 라이브러리",

View file

@ -50,6 +50,7 @@ class MimeTypes {
static const webm = 'video/webm'; static const webm = 'video/webm';
static const json = 'application/json'; static const json = 'application/json';
static const plainText = 'text/plain';
// groups // groups

View file

@ -102,4 +102,7 @@ class AIcons {
static const IconData threeSixty = Icons.threesixty_outlined; static const IconData threeSixty = Icons.threesixty_outlined;
static const IconData selected = Icons.check_circle_outline; static const IconData selected = Icons.check_circle_outline;
static const IconData unselected = Icons.radio_button_unchecked; static const IconData unselected = Icons.radio_button_unchecked;
static const IconData github = MdiIcons.github;
static const IconData legal = MdiIcons.scaleBalance;
} }

View file

@ -38,6 +38,8 @@ class Constants {
static const int infoGroupMaxValueLength = 140; static const int infoGroupMaxValueLength = 140;
static const String avesGithub = 'https://github.com/deckerst/aves';
static const List<Dependency> androidDependencies = [ static const List<Dependency> androidDependencies = [
Dependency( Dependency(
name: 'AndroidX Core-KTX', name: 'AndroidX Core-KTX',
@ -91,6 +93,12 @@ class Constants {
licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/connectivity_plus/connectivity_plus/LICENSE', licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/connectivity_plus/connectivity_plus/LICENSE',
sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/connectivity_plus', sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/connectivity_plus',
), ),
Dependency(
name: 'Device Info Plus',
license: 'BSD 3-Clause',
licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/device_info_plus/device_info_plus/LICENSE',
sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus',
),
Dependency( Dependency(
name: 'FlutterFire (Core, Crashlytics)', name: 'FlutterFire (Core, Crashlytics)',
license: 'BSD 3-Clause', license: 'BSD 3-Clause',

View file

@ -1,4 +1,5 @@
import 'package:aves/widgets/about/app_ref.dart'; import 'package:aves/widgets/about/app_ref.dart';
import 'package:aves/widgets/about/bug_report.dart';
import 'package:aves/widgets/about/credits.dart'; import 'package:aves/widgets/about/credits.dart';
import 'package:aves/widgets/about/licenses.dart'; import 'package:aves/widgets/about/licenses.dart';
import 'package:aves/widgets/about/update.dart'; import 'package:aves/widgets/about/update.dart';
@ -27,6 +28,8 @@ class AboutPage extends StatelessWidget {
AppReference(), AppReference(),
Divider(), Divider(),
AboutUpdate(), AboutUpdate(),
BugReport(),
Divider(),
AboutCredits(), AboutCredits(),
Divider(), Divider(),
], ],

View file

@ -1,6 +1,7 @@
import 'dart:ui'; import 'dart:ui';
import 'package:aves/flutter_version.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/basic/link_chip.dart'; import 'package:aves/widgets/common/basic/link_chip.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_logo.dart'; import 'package:aves/widgets/common/identity/aves_logo.dart';
@ -29,8 +30,8 @@ class _AppReferenceState extends State<AppReference> {
child: Column( child: Column(
children: [ children: [
_buildAvesLine(), _buildAvesLine(),
_buildFlutterLine(),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildLinks(),
], ],
), ),
); );
@ -47,37 +48,45 @@ class _AppReferenceState extends State<AppReference> {
return FutureBuilder<PackageInfo>( return FutureBuilder<PackageInfo>(
future: _packageInfoLoader, future: _packageInfoLoader,
builder: (context, snapshot) { builder: (context, snapshot) {
return LinkChip( return Row(
leading: AvesLogo( mainAxisSize: MainAxisSize.min,
size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.25, children: [
), AvesLogo(
text: '${context.l10n.appName} ${snapshot.data?.version}', size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.25,
url: 'https://github.com/deckerst/aves', ),
textStyle: style, const SizedBox(width: 8),
Text(
'${context.l10n.appName} ${snapshot.data?.version}',
style: style,
),
],
); );
}, },
); );
} }
Widget _buildFlutterLine() { Widget _buildLinks() {
final style = DefaultTextStyle.of(context).style; return Wrap(
final subColor = style.color!.withOpacity(.6); crossAxisAlignment: WrapCrossAlignment.center,
spacing: 16,
return Text.rich( children: [
TextSpan( LinkChip(
children: [ leading: const Icon(
WidgetSpan( AIcons.github,
child: Padding( size: 24,
padding: const EdgeInsetsDirectional.only(end: 4),
child: FlutterLogo(
size: style.fontSize! * 1.25,
),
),
), ),
TextSpan(text: '${context.l10n.aboutFlutter} ${version['frameworkVersion']}'), text: context.l10n.aboutLinkSources,
], url: Constants.avesGithub,
), ),
style: TextStyle(color: subColor), LinkChip(
leading: const Icon(
AIcons.legal,
size: 22,
),
text: context.l10n.aboutLinkLicense,
url: '${Constants.avesGithub}/blob/main/LICENSE',
),
],
); );
} }
} }

View file

@ -0,0 +1,163 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:aves/flutter_version.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/services.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
class BugReport extends StatefulWidget {
const BugReport({Key? key}) : super(key: key);
@override
_BugReportState createState() => _BugReportState();
}
class _BugReportState extends State<BugReport> with FeedbackMixin {
late Future<String> _infoLoader;
bool _showInstructions = false;
@override
void initState() {
super.initState();
_infoLoader = _getInfo();
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return ExpansionPanelList(
expansionCallback: (index, isExpanded) {
setState(() => _showInstructions = !isExpanded);
},
expandedHeaderPadding: EdgeInsets.zero,
elevation: 0,
children: [
ExpansionPanel(
headerBuilder: (context, isExpanded) => ConstrainedBox(
constraints: const BoxConstraints(minHeight: 48),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: AlignmentDirectional.centerStart,
child: Text(l10n.aboutBug, style: Constants.titleTextStyle),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStep(1, l10n.aboutBugSaveLogInstruction, l10n.aboutBugSaveLogButton, _saveLogs),
_buildStep(2, l10n.aboutBugCopyInfoInstruction, l10n.aboutBugCopyInfoButton, _copySystemInfo),
FutureBuilder<String>(
future: _infoLoader,
builder: (context, snapshot) {
final info = snapshot.data;
if (info == null) return const SizedBox();
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey.shade800,
border: Border.all(
color: Colors.white,
),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
margin: const EdgeInsets.symmetric(vertical: 8),
child: SelectableText(info));
},
),
_buildStep(3, l10n.aboutBugReportInstruction, l10n.aboutBugReportButton, _goToGithub),
const SizedBox(height: 16),
],
),
),
isExpanded: _showInstructions,
canTapOnHeader: true,
backgroundColor: Colors.transparent,
),
],
);
}
Widget _buildStep(int step, String text, String buttonText, VoidCallback onPressed) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: Theme.of(context).accentColor,
width: AvesFilterChip.outlineWidth,
)),
shape: BoxShape.circle,
),
child: Text('$step'),
),
const SizedBox(width: 8),
Expanded(child: Text(text)),
const SizedBox(width: 8),
OutlinedButton(
onPressed: onPressed,
style: ButtonStyle(
side: MaterialStateProperty.all<BorderSide>(BorderSide(color: Theme.of(context).accentColor)),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
),
child: Text(buttonText),
)
],
),
);
}
Future<String> _getInfo() async {
final packageInfo = await PackageInfo.fromPlatform();
final androidInfo = await DeviceInfoPlugin().androidInfo;
final hasPlayServices = await availability.hasPlayServices;
return [
'Aves version: ${packageInfo.version} (Build ${packageInfo.buildNumber})',
'Flutter version: ${version['frameworkVersion']} (Channel ${version['channel']})',
'Android version: ${androidInfo.version.release} (SDK ${androidInfo.version.sdkInt})',
'Device: ${androidInfo.manufacturer} ${androidInfo.model}',
'Google Play services: ${hasPlayServices ? 'ready' : 'not available'}',
].join('\n');
}
Future<void> _saveLogs() async {
final result = await Process.run('logcat', ['-d']);
final logs = result.stdout;
final success = await storageService.createFile(
'aves-logs-${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}.txt',
MimeTypes.plainText,
Uint8List.fromList(utf8.encode(logs)),
);
if (success != null) {
if (success) {
showFeedback(context, context.l10n.genericSuccessFeedback);
} else {
showFeedback(context, context.l10n.genericFailureFeedback);
}
}
}
Future<void> _copySystemInfo() async {
await Clipboard.setData(ClipboardData(text: await _infoLoader));
showFeedback(context, context.l10n.genericSuccessFeedback);
}
Future<void> _goToGithub() async {
await launch('${Constants.avesGithub}/issues/new');
}
}

View file

@ -62,7 +62,7 @@ class _AboutUpdateState extends State<AboutUpdate> {
WidgetSpan( WidgetSpan(
child: LinkChip( child: LinkChip(
text: context.l10n.aboutUpdateGitHub, text: context.l10n.aboutUpdateGitHub,
url: 'https://github.com/deckerst/aves/releases', url: '${Constants.avesGithub}/releases',
textStyle: const TextStyle(fontWeight: FontWeight.bold), textStyle: const TextStyle(fontWeight: FontWeight.bold),
), ),
alignment: PlaceholderAlignment.middle, alignment: PlaceholderAlignment.middle,

View file

@ -190,6 +190,48 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.1" version: "1.2.1"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
device_info_plus_linux:
dependency: transitive
description:
name: device_info_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
device_info_plus_macos:
dependency: transitive
description:
name: device_info_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
device_info_plus_web:
dependency: transitive
description:
name: device_info_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
device_info_plus_windows:
dependency: transitive
description:
name: device_info_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -19,6 +19,7 @@ dependencies:
# TODO TLAD as of 2021/08/04, null safe version is pre-release # TODO TLAD as of 2021/08/04, null safe version is pre-release
custom_rounded_rectangle_border: '>=0.2.0-nullsafety.0' custom_rounded_rectangle_border: '>=0.2.0-nullsafety.0'
decorated_icon: decorated_icon:
device_info_plus:
equatable: equatable:
event_bus: event_bus:
expansion_tile_card: expansion_tile_card: