Skip to content

Commit

Permalink
Merge pull request #1113 from get10101/feat/add-feedback-button
Browse files Browse the repository at this point in the history
Add feedback button into Settings screen
  • Loading branch information
klochowicz authored Aug 17, 2023
2 parents 7982b85 + 3d77ee4 commit 873fd0b
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 22 deletions.
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ cargo-clippy:
lint-flutter:
cd mobile && flutter analyze --fatal-infos .

alias flutter-lint := lint-flutter

alias fmt := format
format: dprint flutter-format

Expand Down Expand Up @@ -422,4 +424,6 @@ migrate-coordinator: docker
# Re-run database migrations for both app and coordinator
migrate: migrate-app migrate-coordinator

dart: flutter-format lint-flutter

# vim:expandtab:sw=4:ts=4
18 changes: 18 additions & 0 deletions mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PODS:
- device_info_plus (0.0.1):
- Flutter
- Firebase/CoreOnly (10.12.0):
- FirebaseCore (= 10.12.0)
- Firebase/Messaging (10.12.0):
Expand Down Expand Up @@ -32,6 +34,8 @@ PODS:
- GoogleUtilities/UserDefaults (~> 7.8)
- nanopb (< 2.30910.0, >= 2.30908.0)
- Flutter (1.0.0)
- flutter_email_sender (0.0.1):
- Flutter
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_native_splash (0.0.1):
Expand Down Expand Up @@ -75,18 +79,23 @@ PODS:
- FlutterMacOS
- social_share (0.0.1):
- Flutter
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`)
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- social_share (from `.symlinks/plugins/social_share/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

SPEC REPOS:
trunk:
Expand All @@ -101,12 +110,16 @@ SPEC REPOS:
- PromisesObjC

EXTERNAL SOURCES:
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_messaging:
:path: ".symlinks/plugins/firebase_messaging/ios"
Flutter:
:path: Flutter
flutter_email_sender:
:path: ".symlinks/plugins/flutter_email_sender/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_native_splash:
Expand All @@ -121,8 +134,11 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
social_share:
:path: ".symlinks/plugins/social_share/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
Firebase: 07150e75d142fb9399f6777fa56a187b17f833a0
firebase_core: e477125798fc37cd4ab43ca6a8536bf7e0929c00
firebase_messaging: 334d68c3a36b6d4d5cd91e4f42509e0d4ae49828
Expand All @@ -131,6 +147,7 @@ SPEC CHECKSUMS:
FirebaseInstallations: b28af1b9f997f1a799efe818c94695a3728c352f
FirebaseMessaging: bb2c4f6422a753038fe137d90ae7c1af57251316
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2
Expand All @@ -142,6 +159,7 @@ SPEC CHECKSUMS:
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
social_share: 702a5e3842addd22db515aa9e1e00a4b80a0296d
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4

PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b

Expand Down
69 changes: 69 additions & 0 deletions mobile/lib/common/feedback.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'dart:io';
import 'dart:typed_data';

import 'package:device_info_plus/device_info_plus.dart';
import 'package:f_logs/model/flog/flog.dart';
import 'package:feedback/feedback.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';

Future<void> submitFeedback(UserFeedback feedback) async {
final screenshotFilePath = await writeImageToStorage(feedback.screenshot);
final logs = await FLog.exportLogs();

final deviceInfoPlugin = DeviceInfoPlugin();
String info = "";
if (Platform.isAndroid) {
final deviceInfo = await deviceInfoPlugin.androidInfo;
info =
"${deviceInfo.model}, Android ${deviceInfo.version.sdkInt}, Release: ${deviceInfo.version.release}";
} else {
final deviceInfo = await deviceInfoPlugin.iosInfo;
info = "${deviceInfo.name}, ${deviceInfo.systemName} ${deviceInfo.systemVersion}";
}

const feedbackEmail = "[email protected]";
const subject = "10101 Feedback";
final body = '${feedback.text}\n\n----------\n$info';

final Email email = Email(
body: body,
subject: subject,
recipients: [feedbackEmail],
attachmentPaths: [screenshotFilePath, logs.path],
isHTML: false,
);
try {
await FlutterEmailSender.send(email);
} on Exception catch (e) {
// fallback to using mailto link
// We cannot auto-attach images with this, but the user will still be able to provide text feedback
// We add a message that adds the exception to the body text.
final Uri emailLaunchUri = Uri(
scheme: 'mailto',
path: feedbackEmail,
queryParameters: {
'subject': subject,
'body': "$body \n\n----------\n"
"Could not auto-attach images. Had to fallback to mailto link because: $e"
},
);

if (await canLaunchUrl(emailLaunchUri)) {
await launchUrl(emailLaunchUri);
} else {
// If we are unable to use mailto we throw the original exception because it likely contains more useful information.
// If canLaunchUrl returns false this means there is likely no mail application configured, or we are unable to read the intent
rethrow;
}
}
}

Future<String> writeImageToStorage(Uint8List feedbackScreenshot) async {
final Directory output = await getTemporaryDirectory();
final String screenshotFilePath = '${output.path}/feedback.png';
final File screenshotFile = File(screenshotFilePath);
await screenshotFile.writeAsBytes(feedbackScreenshot);
return screenshotFilePath;
}
14 changes: 14 additions & 0 deletions mobile/lib/common/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import 'dart:convert';
import 'dart:io';

import 'package:f_logs/f_logs.dart';
import 'package:feedback/feedback.dart';
import 'package:flutter/material.dart';
import 'package:get_10101/common/feedback.dart';
import 'package:get_10101/common/scrollable_safe_area.dart';
import 'package:get_10101/features/trade/settings_screen.dart';
import 'package:get_10101/features/wallet/settings_screen.dart';
Expand Down Expand Up @@ -188,6 +190,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
Share.shareXFiles([logFile], text: 'Logs from $now');
},
child: const Text("Share logs")),
const SizedBox(height: 20),
ElevatedButton(
child: const Text('Provide feedback'),
onPressed: () {
try {
BetterFeedback.of(context).show(submitFeedback);
} on Exception catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to share feedback via email app because: $e')));
}
},
),
])),
);
}
Expand Down
41 changes: 23 additions & 18 deletions mobile/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:f_logs/f_logs.dart';
import 'package:feedback/feedback.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
Expand Down Expand Up @@ -61,6 +62,9 @@ import 'package:version/version.dart';
final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _shellNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'shell');

final GlobalKey<NavigatorState> _feedbackNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'feedback');

void main() {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
Expand Down Expand Up @@ -283,24 +287,25 @@ class _TenTenOneAppState extends State<TenTenOneApp> {
@override
Widget build(BuildContext context) {
MaterialColor swatch = tenTenOnePurple;

return MaterialApp.router(
title: "10101",
scaffoldMessengerKey: scaffoldMessengerKey,
theme: ThemeData(
primarySwatch: swatch,
iconTheme: IconThemeData(
color: tenTenOnePurple.shade800,
size: 32,
),
extensions: <ThemeExtension<dynamic>>[
const TradeTheme(),
WalletTheme(colors: ColorScheme.fromSwatch(primarySwatch: swatch)),
],
),
routerConfig: _router,
debugShowCheckedModeBanner: false,
);
return BetterFeedback(
key: _feedbackNavigatorKey,
child: MaterialApp.router(
title: "10101",
scaffoldMessengerKey: scaffoldMessengerKey,
theme: ThemeData(
primarySwatch: swatch,
iconTheme: IconThemeData(
color: tenTenOnePurple.shade800,
size: 32,
),
extensions: <ThemeExtension<dynamic>>[
const TradeTheme(),
WalletTheme(colors: ColorScheme.fromSwatch(primarySwatch: swatch)),
],
),
routerConfig: _router,
debugShowCheckedModeBanner: false,
));
}

Future<void> init(bridge.Config config) async {
Expand Down
4 changes: 4 additions & 0 deletions mobile/macos/Flutter/GeneratedPluginRegistrant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@
import FlutterMacOS
import Foundation

import device_info_plus
import firebase_core
import firebase_messaging
import flutter_local_notifications
import package_info_plus
import path_provider_foundation
import share_plus
import shared_preferences_foundation
import url_launcher_macos

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
Loading

0 comments on commit 873fd0b

Please sign in to comment.