Skip to content

Commit

Permalink
initial refactor to move to typed API
Browse files Browse the repository at this point in the history
  • Loading branch information
caseycrogers committed Nov 15, 2024
1 parent 7893889 commit 38ae0c7
Show file tree
Hide file tree
Showing 19 changed files with 432 additions and 385 deletions.
2 changes: 1 addition & 1 deletion feedback/example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
<string>12.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion feedback/example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# platform :ios, '12.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
17 changes: 10 additions & 7 deletions feedback/example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -164,7 +164,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
Expand Down Expand Up @@ -214,14 +214,14 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/flutter_email_sender/flutter_email_sender.framework",
"${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
"${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework",
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_email_sender.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
);
Expand All @@ -232,10 +232,12 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
Expand All @@ -246,6 +248,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand Down Expand Up @@ -355,7 +358,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -440,7 +443,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -489,7 +492,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
2 changes: 1 addition & 1 deletion feedback/example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UIKit
import Flutter

@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
Expand Down
2 changes: 2 additions & 0 deletions feedback/example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,7 @@
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
46 changes: 26 additions & 20 deletions feedback/example/lib/custom_feedback.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import 'dart:typed_data';

import 'package:feedback/feedback.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

/// A data type holding user feedback consisting of a feedback type, free from
Expand Down Expand Up @@ -53,11 +56,11 @@ class CustomFeedbackForm extends StatefulWidget {
const CustomFeedbackForm({
super.key,
required this.onSubmit,
required this.scrollController,
required this.formController,
});

final OnSubmit onSubmit;
final ScrollController? scrollController;
final FeedbackFormController<UserFeedback> formController;

@override
State<CustomFeedbackForm> createState() => _CustomFeedbackFormState();
Expand All @@ -73,13 +76,11 @@ class _CustomFeedbackFormState extends State<CustomFeedbackForm> {
Expanded(
child: Stack(
children: [
if (widget.scrollController != null)
const FeedbackSheetDragHandle(),
if (widget.formController.scrollController != null) const FeedbackSheetDragHandle(),
ListView(
controller: widget.scrollController,
controller: widget.formController.scrollController,
// Pad the top by 20 to match the corner radius if drag enabled.
padding: EdgeInsets.fromLTRB(
16, widget.scrollController != null ? 20 : 16, 16, 0),
padding: EdgeInsets.fromLTRB(16, widget.formController.scrollController != null ? 20 : 16, 16, 0),
children: [
const Text('What kind of feedback do you want to give?'),
Row(
Expand All @@ -99,16 +100,11 @@ class _CustomFeedbackFormState extends State<CustomFeedbackForm> {
.map(
(type) => DropdownMenuItem<FeedbackType>(
value: type,
child: Text(type
.toString()
.split('.')
.last
.replaceAll('_', ' ')),
child: Text(type.toString().split('.').last.replaceAll('_', ' ')),
),
)
.toList(),
onChanged: (feedbackType) => setState(() =>
_customFeedback.feedbackType = feedbackType),
onChanged: (feedbackType) => setState(() => _customFeedback.feedbackType = feedbackType),
),
ElevatedButton(
child: const Text('Open Dialog #2'),
Expand All @@ -132,8 +128,7 @@ class _CustomFeedbackFormState extends State<CustomFeedbackForm> {
const SizedBox(height: 16),
const Text('What is your feedback?'),
TextField(
onChanged: (newFeedback) =>
_customFeedback.feedbackText = newFeedback,
onChanged: (newFeedback) => _customFeedback.feedbackText = newFeedback,
),
const SizedBox(height: 16),
const Text('How does this make you feel?'),
Expand All @@ -149,10 +144,21 @@ class _CustomFeedbackFormState extends State<CustomFeedbackForm> {
TextButton(
// disable this button until the user has specified a feedback type
onPressed: _customFeedback.feedbackType != null
? () => widget.onSubmit(
_customFeedback.feedbackText ?? '',
extras: _customFeedback.toMap(),
)
? () async {
final Uint8List screenshot = await widget.formController.takeScreenshot(context);
if (!context.mounted) {
// User popped the page while screenshotting, abort.
return;
}
await widget.onSubmit(
context,
UserFeedback(
text: _customFeedback.feedbackText ?? '',
screenshot: screenshot,
extra: _customFeedback.toMap(),
),
);
}
: null,
child: const Text('submit'),
),
Expand Down
108 changes: 100 additions & 8 deletions feedback/example/lib/feedback_functions.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,62 @@
// ignore_for_file: avoid_print

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:feedback/feedback.dart';
import 'package:flutter/material.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';

/// Map from a string route to the underlying feedback route and it's corresponding `onSubmit`.
/// This is just here to support the legacy `simpleFeedback` API.
Future<void> onSubmitWithStringRoute(
BuildContext context,
String? route,
UserFeedback feedback,
) {
return switch (route) {
'alert_dialog' => onSubmitAlertDialog(context, feedback),
'email' => onSubmitEmail(context, feedback),
'platform_sharing' => onSubmitPlatformSharing(context, feedback),
_ => throw UnsupportedError('Unsupported route: `$route`'),
};
}

Future<void> onSubmitEmail(BuildContext context, UserFeedback feedback) async {
// draft an email and send to developer
final screenshotFilePath = await writeImageToStorage(feedback.screenshot);

final Email email = Email(
body: feedback.text,
subject: 'App Feedback',
recipients: ['[email protected]'],
attachmentPaths: [screenshotFilePath],
isHTML: false,
);
await FlutterEmailSender.send(email);
}

Future<void> onSubmitPlatformSharing(BuildContext context, UserFeedback feedback) async {
final screenshotFilePath = await writeImageToStorage(feedback.screenshot);

// ignore: deprecated_member_use
await Share.shareFiles(
[screenshotFilePath],
text: feedback.text,
);
}

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;
}

/// Prints the given feedback to the console.
/// This is useful for debugging purposes.
Expand All @@ -17,14 +72,9 @@ void consoleFeedbackFunction(
}
}

/// Shows an [AlertDialog] with the given feedback.
/// This is useful for debugging purposes.
void alertFeedbackFunction(
BuildContext outerContext,
UserFeedback feedback,
) {
showDialog<void>(
context: outerContext,
Future<void> onSubmitAlertDialog(BuildContext context, UserFeedback feedback) async {
await showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(feedback.text),
Expand Down Expand Up @@ -54,3 +104,45 @@ void alertFeedbackFunction(
},
);
}

Future<void> createGitlabIssueFromFeedback(BuildContext context, UserFeedback feedback) async {
const projectId = 'your-gitlab-project-id';
const apiToken = 'your-gitlab-api-token';

final screenshotFilePath = await writeImageToStorage(feedback.screenshot);

// Upload screenshot
final uploadRequest = http.MultipartRequest(
'POST',
Uri.https(
'gitlab.com',
'/api/v4/projects/$projectId/uploads',
),
)
..files.add(await http.MultipartFile.fromPath(
'file',
screenshotFilePath,
))
..headers.putIfAbsent('PRIVATE-TOKEN', () => apiToken);
final uploadResponse = await uploadRequest.send();

final dynamic uploadResponseMap = jsonDecode(
await uploadResponse.stream.bytesToString(),
);

// Create issue
await http.post(
Uri.https(
'gitlab.com',
'/api/v4/projects/$projectId/issues',
<String, String>{
'title': feedback.text.padRight(80),
'description': '${feedback.text}\n'
"${uploadResponseMap["markdown"] ?? "Missing image!"}",
},
),
headers: {
'PRIVATE-TOKEN': apiToken,
},
);
}
Loading

0 comments on commit 38ae0c7

Please sign in to comment.