diff --git a/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt b/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt index f6fb0fc03..1bbec12b7 100644 --- a/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt +++ b/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt @@ -24,12 +24,12 @@ class InstabugExampleMethodCallHandler : MethodChannel.MethodCallHandler { } SEND_NATIVE_FATAL_HANG -> { Log.d(TAG, "Sending native fatal hang for 3000 ms") - sendANR() + sendFatalHang() result.success(null) } SEND_ANR -> { Log.d(TAG, "Sending android not responding 'ANR' hanging for 20000 ms") - sendFatalHang() + sendANR() result.success(null) } SEND_OOM -> { @@ -78,60 +78,43 @@ class InstabugExampleMethodCallHandler : MethodChannel.MethodCallHandler { } private fun sendANR() { + android.os.Handler(Looper.getMainLooper()).post { try { Thread.sleep(20000) } catch (e: InterruptedException) { throw RuntimeException(e) } + } } private fun sendFatalHang() { - try { - Thread.sleep(3000) - } catch (e: InterruptedException) { - throw RuntimeException(e) + android.os.Handler(Looper.getMainLooper()).post { + + try { + Thread.sleep(3000) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } } } private fun sendOOM() { + android.os.Handler(Looper.getMainLooper()).post { + + oomCrash() + } } private fun oomCrash() { - Thread { - val stringList: MutableList = ArrayList() - for (i in 0 until 1000000) { - stringList.add(getRandomString(10000)) - } - }.start() - } - - private fun getRandomString(length: Int): String { - val charset: MutableList = ArrayList() - var ch = 'a' - while (ch <= 'z') { - charset.add(ch) - ch++ + val list = ArrayList() + while (true) { + list.add(ByteArray(10 * 1024 * 1024)) // Allocate 10MB chunks } - ch = 'A' - while (ch <= 'Z') { - charset.add(ch) - ch++ - } - ch = '0' - while (ch <= '9') { - charset.add(ch) - ch++ - } - val randomString = StringBuilder() - val random = java.util.Random() - for (i in 0 until length) { - val randomChar = charset[random.nextInt(charset.size)] - randomString.append(randomChar) - } - return randomString.toString() } + + private fun setFullscreen(enabled: Boolean) { try { diff --git a/example/ios/Runner/InstabugExampleMethodCallHandler.m b/example/ios/Runner/InstabugExampleMethodCallHandler.m index 3e4ee70d6..0dbbc56a2 100644 --- a/example/ios/Runner/InstabugExampleMethodCallHandler.m +++ b/example/ios/Runner/InstabugExampleMethodCallHandler.m @@ -55,19 +55,21 @@ + (BOOL)requiresMainQueueSetup } - (void)oomCrash { - dispatch_async(self.serialQueue, ^{ - self.oomBelly = [NSMutableArray array]; - [UIApplication.sharedApplication beginBackgroundTaskWithName:@"OOM Crash" expirationHandler:nil]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSMutableArray *belly = [NSMutableArray array]; while (true) { - unsigned long dinnerLength = 1024 * 1024 * 10; - char *dinner = malloc(sizeof(char) * dinnerLength); - for (int i=0; i < dinnerLength; i++) - { - //write to each byte ensure that the memory pages are actually allocated - dinner[i] = '0'; + // 20 MB chunks to speed up OOM + void *buffer = malloc(20 * 1024 * 1024); + if (buffer == NULL) { + NSLog(@"OOM: malloc failed"); + break; } - NSData *plate = [NSData dataWithBytesNoCopy:dinner length:dinnerLength freeWhenDone:YES]; - [self.oomBelly addObject:plate]; + memset(buffer, 1, 20 * 1024 * 1024); + NSData *data = [NSData dataWithBytesNoCopy:buffer length:20 * 1024 * 1024 freeWhenDone:YES]; + [belly addObject:data]; + + // Optional: slight delay to avoid CPU spike + [NSThread sleepForTimeInterval:0.01]; } }); } @@ -108,7 +110,9 @@ - (void)sendNativeFatalCrash { } - (void)sendFatalHang { - [NSThread sleepForTimeInterval:3.0f]; + dispatch_async(dispatch_get_main_queue(), ^{ + sleep(20); // Block main thread for 20 seconds + }); } - (void)sendOOM { diff --git a/example/lib/main.dart b/example/lib/main.dart index bccdbfc68..199806134 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,13 +2,19 @@ import 'dart:async'; import 'dart:developer'; import 'dart:io'; import 'dart:convert'; +import 'dart:math' as math; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; import 'package:instabug_flutter_example/src/components/apm_switch.dart'; +import 'package:instabug_flutter_example/src/screens/callback/callback_handler_provider.dart'; +import 'package:instabug_flutter_example/src/screens/callback/callback_page.dart'; +import 'package:instabug_flutter_example/src/utils/widget_ext.dart'; import 'package:instabug_http_client/instabug_http_client.dart'; import 'package:instabug_flutter_example/src/app_routes.dart'; import 'package:instabug_flutter_example/src/widget/nested_view.dart'; +import 'package:provider/provider.dart'; import 'src/native/instabug_flutter_example_method_channel.dart'; import 'src/widget/instabug_button.dart'; @@ -20,6 +26,10 @@ import 'src/widget/section_title.dart'; part 'src/screens/crashes_page.dart'; +part 'src/screens/bug_reporting.dart'; + +part 'src/screens/session_replay_page.dart'; + part 'src/screens/complex_page.dart'; part 'src/screens/apm_page.dart'; @@ -55,7 +65,12 @@ void main() { Zone.current.handleUncaughtError(details.exception, details.stack!); }; - runApp(const MyApp()); + runApp( + ChangeNotifierProvider( + create: (_) => CallbackHandlersProvider(), + child: const MyApp(), + ), + ); }, CrashReporting.reportCrash, ); diff --git a/example/lib/src/app_routes.dart b/example/lib/src/app_routes.dart index 9175d5405..e348b1a86 100644 --- a/example/lib/src/app_routes.dart +++ b/example/lib/src/app_routes.dart @@ -1,5 +1,6 @@ import 'package:flutter/widgets.dart' show BuildContext; import 'package:instabug_flutter_example/main.dart'; +import 'package:instabug_flutter_example/src/screens/callback/callback_page.dart'; final appRoutes = { /// ["/"] route name should only be used with [onGenerateRoute:] when no @@ -9,7 +10,14 @@ final appRoutes = { "/": (BuildContext context) => const MyHomePage(title: 'Flutter Demo Home Pag'), CrashesPage.screenName: (BuildContext context) => const CrashesPage(), + BugReportingPage.screenName: (BuildContext context) => + const BugReportingPage(), + CallbackScreen.screenName: (BuildContext context) => const CallbackScreen(), ComplexPage.screenName: (BuildContext context) => const ComplexPage(), + SessionReplayPage.screenName: (BuildContext context) => + const SessionReplayPage(), + TopTabBarScreen.route: (BuildContext context) => const TopTabBarScreen(), + ApmPage.screenName: (BuildContext context) => const ApmPage(), ScreenLoadingPage.screenName: (BuildContext context) => const ScreenLoadingPage(), diff --git a/example/lib/src/components/fatal_crashes_content.dart b/example/lib/src/components/fatal_crashes_content.dart index c262b63a6..d1a1048b2 100644 --- a/example/lib/src/components/fatal_crashes_content.dart +++ b/example/lib/src/components/fatal_crashes_content.dart @@ -19,54 +19,74 @@ class FatalCrashesContent extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ InstabugButton( + symanticLabel: 'fatal_crash_exception', text: 'Throw Exception', + key: const Key('fatal_crash_exception'), onPressed: () => throwUnhandledException( Exception('This is a generic exception.')), ), InstabugButton( + symanticLabel: 'fatal_crash_state_exception', text: 'Throw StateError', + key: const Key('fatal_crash_state_exception'), onPressed: () => throwUnhandledException(StateError('This is a StateError.')), ), InstabugButton( + symanticLabel: 'fatal_crash_argument_exception', text: 'Throw ArgumentError', + key: const Key('fatal_crash_argument_exception'), onPressed: () => throwUnhandledException( ArgumentError('This is an ArgumentError.')), ), InstabugButton( + symanticLabel: 'fatal_crash_range_exception', text: 'Throw RangeError', + key: const Key('fatal_crash_range_exception'), onPressed: () => throwUnhandledException( RangeError.range(5, 0, 3, 'Index out of range')), ), InstabugButton( + symanticLabel: 'fatal_crash_format_exception', text: 'Throw FormatException', + key: const Key('fatal_crash_format_exception'), onPressed: () => throwUnhandledException(UnsupportedError('Invalid format.')), ), InstabugButton( + symanticLabel: 'fatal_crash_no_such_method_error_exception', text: 'Throw NoSuchMethodError', + key: const Key('fatal_crash_no_such_method_error_exception'), onPressed: () { // This intentionally triggers a NoSuchMethodError dynamic obj; throwUnhandledException(obj.methodThatDoesNotExist()); }, ), - const InstabugButton( + InstabugButton( + symanticLabel: 'fatal_crash_native_exception', text: 'Throw Native Fatal Crash', + key: const Key('fatal_crash_native_exception'), onPressed: InstabugFlutterExampleMethodChannel.sendNativeFatalCrash, ), - const InstabugButton( + InstabugButton( + symanticLabel: 'fatal_crash_native_hang', text: 'Send Native Fatal Hang', + key: const Key('fatal_crash_native_hang'), onPressed: InstabugFlutterExampleMethodChannel.sendNativeFatalHang, ), Platform.isAndroid - ? const InstabugButton( + ? InstabugButton( + symanticLabel: 'fatal_crash_anr', text: 'Send Native ANR', + key: const Key('fatal_crash_anr'), onPressed: InstabugFlutterExampleMethodChannel.sendAnr, ) : const SizedBox.shrink(), - const InstabugButton( + InstabugButton( + symanticLabel: 'fatal_crash_oom', text: 'Throw Unhandled Native OOM Exception', + key: const Key('fatal_crash_oom'), onPressed: InstabugFlutterExampleMethodChannel.sendOom, ), ], diff --git a/example/lib/src/components/flows_content.dart b/example/lib/src/components/flows_content.dart index ecd8163f9..c0cbea7fe 100644 --- a/example/lib/src/components/flows_content.dart +++ b/example/lib/src/components/flows_content.dart @@ -21,6 +21,7 @@ class _FlowsContentState extends State { children: [ InstabugTextField( label: 'Flow name', + symanticLabel: 'flow_name_input', labelStyle: textTheme.labelMedium, controller: flowNameController, ), @@ -33,6 +34,7 @@ class _FlowsContentState extends State { flex: 5, child: InstabugButton.smallFontSize( text: 'Start Flow', + symanticLabel: 'start_flow', onPressed: () => _startFlow(flowNameController.text), margin: const EdgeInsetsDirectional.only( start: 20.0, @@ -44,6 +46,7 @@ class _FlowsContentState extends State { flex: 5, child: InstabugButton.smallFontSize( text: 'Start flow With Delay', + symanticLabel: 'start_flow_with_delay', onPressed: () => _startFlow( flowNameController.text, delayInMilliseconds: 5000, @@ -62,6 +65,7 @@ class _FlowsContentState extends State { flex: 5, child: InstabugTextField( label: 'Flow Key Attribute', + symanticLabel: 'flow_key_input', controller: flowKeyAttributeController, labelStyle: textTheme.labelMedium, margin: const EdgeInsetsDirectional.only( @@ -74,6 +78,7 @@ class _FlowsContentState extends State { flex: 5, child: InstabugTextField( label: 'Flow Value Attribute', + symanticLabel: 'flow_value_input', labelStyle: textTheme.labelMedium, controller: flowValueAttributeController, margin: const EdgeInsetsDirectional.only( @@ -89,6 +94,7 @@ class _FlowsContentState extends State { ), InstabugButton( text: 'Set Flow Attribute', + symanticLabel: 'set_flow_attribute', onPressed: () => _setFlowAttribute( flowNameController.text, flowKeyAttribute: flowKeyAttributeController.text, @@ -97,6 +103,7 @@ class _FlowsContentState extends State { ), InstabugButton( text: 'End Flow', + symanticLabel: 'end_flow', onPressed: () => _endFlow(flowNameController.text), ), ], diff --git a/example/lib/src/components/network_content.dart b/example/lib/src/components/network_content.dart index 23eaa4dd2..ea432162a 100644 --- a/example/lib/src/components/network_content.dart +++ b/example/lib/src/components/network_content.dart @@ -20,22 +20,92 @@ class _NetworkContentState extends State { children: [ InstabugClipboardInput( label: 'Endpoint Url', + symanticLabel: 'endpoint_url_input', controller: endpointUrlController, ), InstabugButton( text: 'Send Request To Url', + symanticLabel: 'make_http_request', onPressed: () => _sendRequestToUrl(endpointUrlController.text), ), + InstabugButton( + text: 'Send Get Request ', + symanticLabel: 'make_get_request', + onPressed: () => _sendGetRequestToUrl('https://httpbin.org/get'), + ), + InstabugButton( + text: 'Send Post Request ', + symanticLabel: 'make_post_request', + onPressed: () => _sendPostRequestToUrl('https://httpbin.org/post'), + ), + InstabugButton( + text: 'Send put Request ', + symanticLabel: 'make_put_request', + onPressed: () => _sendPutRequestToUrl('https://httpbin.org/put'), + ), + InstabugButton( + text: 'Send delete Request ', + symanticLabel: 'make_delete_request', + onPressed: () => + _sendDeleteRequestToUrl('https://httpbin.org/delete'), + ), + InstabugButton( + text: 'Send patch Request ', + symanticLabel: 'make_patch_request', + onPressed: () => _sendPatchRequestToUrl('https://httpbin.org/patch'), + ), const Text("W3C Header Section"), InstabugButton( text: 'Send Request With Custom traceparent header', + symanticLabel: 'make_http_request_with_traceparent_header', onPressed: () => _sendRequestToUrl(endpointUrlController.text, headers: {"traceparent": "Custom traceparent header"}), ), InstabugButton( text: 'Send Request Without Custom traceparent header', + symanticLabel: 'make_http_request_with_w3c_header', onPressed: () => _sendRequestToUrl(endpointUrlController.text), ), + InstabugButton( + text: 'obfuscateLog', + symanticLabel: 'obfuscate_log', + onPressed: () { + NetworkLogger.obfuscateLog((networkData) async { + return networkData.copyWith(url: 'fake url'); + }); + }, + ), + InstabugButton( + text: 'omitLog', + symanticLabel: 'omit_log', + onPressed: () { + NetworkLogger.omitLog((networkData) async { + return networkData.url.contains('google.com'); + }); + }, + ), + InstabugButton( + text: 'obfuscateLogWithException', + symanticLabel: 'obfuscate_log_with_exception', + onPressed: () { + NetworkLogger.obfuscateLog((networkData) async { + throw Exception("obfuscateLogWithException"); + + return networkData.copyWith(url: 'fake url'); + }); + }, + ), + InstabugButton( + text: 'omitLogWithException', + symanticLabel: 'omit_log_with_exception', + onPressed: () { + NetworkLogger.omitLog((networkData) async { + throw Exception("OmitLog with exception"); + + return networkData.url.contains('google.com'); + }); + }, + ), ], ); } @@ -56,4 +126,92 @@ class _NetworkContentState extends State { log('Error sending request: $e'); } } + + void _sendGetRequestToUrl(String text, {Map? headers}) async { + try { + String url = text.trim().isEmpty ? widget.defaultRequestUrl : text; + final response = await http.get(Uri.parse(url), headers: headers); + + // Handle the response here + if (response.statusCode == 200) { + final jsonData = json.decode(response.body); + log(jsonEncode(jsonData)); + } else { + log('Request failed with status: ${response.statusCode}'); + } + } catch (e) { + log('Error sending request: $e'); + } + } + + void _sendPostRequestToUrl(String text, + {Map? headers}) async { + try { + String url = text.trim().isEmpty ? widget.defaultRequestUrl : text; + final response = await http.post(Uri.parse(url), headers: headers); + + // Handle the response here + if (response.statusCode == 200) { + final jsonData = json.decode(response.body); + log(jsonEncode(jsonData)); + } else { + log('Request failed with status: ${response.statusCode}'); + } + } catch (e) { + log('Error sending request: $e'); + } + } + + void _sendPutRequestToUrl(String text, {Map? headers}) async { + try { + String url = text.trim().isEmpty ? widget.defaultRequestUrl : text; + final response = await http.put(Uri.parse(url), headers: headers); + + // Handle the response here + if (response.statusCode == 200) { + final jsonData = json.decode(response.body); + log(jsonEncode(jsonData)); + } else { + log('Request failed with status: ${response.statusCode}'); + } + } catch (e) { + log('Error sending request: $e'); + } + } + + void _sendDeleteRequestToUrl(String text, + {Map? headers}) async { + try { + String url = text.trim().isEmpty ? widget.defaultRequestUrl : text; + final response = await http.delete(Uri.parse(url), headers: headers); + + // Handle the response here + if (response.statusCode == 200) { + final jsonData = json.decode(response.body); + log(jsonEncode(jsonData)); + } else { + log('Request failed with status: ${response.statusCode}'); + } + } catch (e) { + log('Error sending request: $e'); + } + } + + void _sendPatchRequestToUrl(String text, + {Map? headers}) async { + try { + String url = text.trim().isEmpty ? widget.defaultRequestUrl : text; + final response = await http.patch(Uri.parse(url), headers: headers); + + // Handle the response here + if (response.statusCode == 200) { + final jsonData = json.decode(response.body); + log(jsonEncode(jsonData)); + } else { + log('Request failed with status: ${response.statusCode}'); + } + } catch (e) { + log('Error sending request: $e'); + } + } } diff --git a/example/lib/src/components/non_fatal_crashes_content.dart b/example/lib/src/components/non_fatal_crashes_content.dart index c3d331187..19a6a7476 100644 --- a/example/lib/src/components/non_fatal_crashes_content.dart +++ b/example/lib/src/components/non_fatal_crashes_content.dart @@ -42,38 +42,52 @@ class _NonFatalCrashesContentState extends State { children: [ InstabugButton( text: 'Throw Exception', + key: const Key('non_fatal_exception'), + symanticLabel: 'non_fatal_exception', onPressed: () => throwHandledException(Exception('This is a generic exception.')), ), InstabugButton( text: 'Throw StateError', + key: const Key('non_fatal_state_exception'), + symanticLabel: 'non_fatal_state_exception', onPressed: () => throwHandledException(StateError('This is a StateError.')), ), InstabugButton( text: 'Throw ArgumentError', + key: const Key('non_fatal_argument_exception'), + symanticLabel: 'non_fatal_argument_exception', onPressed: () => throwHandledException(ArgumentError('This is an ArgumentError.')), ), InstabugButton( text: 'Throw RangeError', + key: const Key('non_fatal_range_exception'), + symanticLabel: 'non_fatal_range_exception', onPressed: () => throwHandledException( RangeError.range(5, 0, 3, 'Index out of range')), ), InstabugButton( text: 'Throw FormatException', + key: const Key('non_fatal_format_exception'), + symanticLabel: 'non_fatal_format_exception', onPressed: () => throwHandledException(UnsupportedError('Invalid format.')), ), InstabugButton( text: 'Throw NoSuchMethodError', + symanticLabel: 'non_fatal_no_such_method_exception', + key: const Key('non_fatal_no_such_method_exception'), onPressed: () { dynamic obj; throwHandledException(obj.methodThatDoesNotExist()); }, ), - const InstabugButton( + InstabugButton( text: 'Throw Handled Native Exception', + symanticLabel: 'non_fatal_native_exception', + key: const Key('non_fatal_native_exception'), onPressed: InstabugFlutterExampleMethodChannel.sendNativeNonFatalCrash, ), @@ -86,6 +100,8 @@ class _NonFatalCrashesContentState extends State { Expanded( child: InstabugTextField( label: "Crash title", + symanticLabel: 'non_fatal_crash_title_textfield', + key: const Key("non_fatal_crash_title_textfield"), controller: crashNameController, validator: (value) { if (value?.trim().isNotEmpty == true) return null; @@ -100,6 +116,8 @@ class _NonFatalCrashesContentState extends State { Expanded( child: InstabugTextField( label: "User Attribute key", + symanticLabel: 'non_fatal_user_attribute_key_textfield', + key: const Key("non_fatal_user_attribute_key_textfield"), controller: crashUserAttributeKeyController, validator: (value) { if (crashUserAttributeValueController.text.isNotEmpty) { @@ -112,7 +130,9 @@ class _NonFatalCrashesContentState extends State { )), Expanded( child: InstabugTextField( - label: "User Attribute Value", + label: "User Attribute Value", + symanticLabel: 'non_fatal_user_attribute_value_textfield', + key: const Key("non_fatal_user_attribute_value_textfield"), controller: crashUserAttributeValueController, validator: (value) { if (crashUserAttributeKeyController.text.isNotEmpty) { @@ -130,6 +150,10 @@ class _NonFatalCrashesContentState extends State { Expanded( child: InstabugTextField( label: "Fingerprint", + symanticLabel: + 'non_fatal_user_attribute_fingerprint_textfield', + key: const Key( + "non_fatal_user_attribute_fingerprint_textfield"), controller: crashfingerPrintController, )), ], @@ -141,6 +165,7 @@ class _NonFatalCrashesContentState extends State { Expanded( flex: 5, child: DropdownButtonHideUnderline( + key: const Key("non_fatal_crash_level_dropdown"), child: DropdownButtonFormField( value: crashType, @@ -157,16 +182,17 @@ class _NonFatalCrashesContentState extends State { crashType = value!; }, ), - )), + ).withSemanticsLabel('non_fatal_crash_level_dropdown')), ], ), ), - SizedBox( + const SizedBox( height: 8, ), InstabugButton( text: 'Send Non Fatal Crash', onPressed: sendNonFatalCrash, + symanticLabel: 'send_non_fatal_crash', ) ], ), @@ -190,7 +216,7 @@ class _NonFatalCrashesContentState extends State { fingerprint: crashfingerPrintController.text, level: crashType); ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text("Crash sent"))); + .showSnackBar(const SnackBar(content: Text("Crash sent"))); crashNameController.text = ''; crashfingerPrintController.text = ''; crashUserAttributeValueController.text = ''; diff --git a/example/lib/src/screens/apm_page.dart b/example/lib/src/screens/apm_page.dart index d4ad53ca6..be3d6d681 100644 --- a/example/lib/src/screens/apm_page.dart +++ b/example/lib/src/screens/apm_page.dart @@ -32,6 +32,7 @@ class _ApmPageState extends State { const APMSwitch(), InstabugButton( text: 'End App Launch', + symanticLabel: 'end_app_launch', onPressed: _endAppLaunch, ), const SectionTitle('Network'), @@ -45,6 +46,7 @@ class _ApmPageState extends State { InstabugButton( text: 'Screen Loading', onPressed: _navigateToScreenLoading, + symanticLabel: 'end_screen_loading', ), SizedBox.fromSize( size: const Size.fromHeight(12), diff --git a/example/lib/src/screens/bug_reporting.dart b/example/lib/src/screens/bug_reporting.dart new file mode 100644 index 000000000..182c72470 --- /dev/null +++ b/example/lib/src/screens/bug_reporting.dart @@ -0,0 +1,599 @@ +part of '../../main.dart'; + +class BugReportingPage extends StatefulWidget { + static const screenName = 'bugReporting'; + + const BugReportingPage({Key? key}) : super(key: key); + + @override + _BugReportingPageState createState() => _BugReportingPageState(); +} + +class _BugReportingPageState extends State { + List reportTypes = [ + ReportType.bug, + ReportType.feedback, + ReportType.question + ]; + List invocationOptions = []; + + final disclaimerTextController = TextEditingController(); + + bool attachmentsOptionsScreenshot = true; + bool attachmentsOptionsExtraScreenshot = true; + bool attachmentsOptionsGalleryImage = true; + bool attachmentsOptionsScreenRecording = true; + File? fileAttachment; + CallbackHandlersProvider? callbackHandlerProvider; + + void restartInstabug() { + Instabug.setEnabled(false); + Instabug.setEnabled(true); + BugReporting.setInvocationEvents([InvocationEvent.floatingButton]); + } + + void setInvocationEvent(InvocationEvent invocationEvent) { + BugReporting.setInvocationEvents([invocationEvent]); + } + + @override + void initState() { + super.initState(); + } + + void setUserConsent( + String key, + String description, + bool mandatory, + bool checked, + UserConsentActionType? actionType, + ) { + BugReporting.addUserConsents( + key: key, + description: description, + mandatory: mandatory, + checked: true, + actionType: actionType); + } + + void show() { + Instabug.show(); + } + + Future addFileAttachment() async { + FilePickerResult? result = await FilePicker.platform.pickFiles(); + + if (result != null) { + fileAttachment = File(result.files.single.path!); + Instabug.addFileAttachmentWithURL(fileAttachment!.path, + fileAttachment!.path.split('/').last.substring(0)); + setState(() {}); + } + } + + void removeFileAttachment() { + Instabug.clearFileAttachments(); + } + + void addAttachmentOptions() { + BugReporting.setEnabledAttachmentTypes( + attachmentsOptionsScreenshot, + attachmentsOptionsExtraScreenshot, + attachmentsOptionsGalleryImage, + attachmentsOptionsScreenRecording); + } + + void toggleReportType(ReportType reportType) { + if (reportTypes.contains(reportType)) { + reportTypes.remove(reportType); + } else { + reportTypes.add(reportType); + } + setState(() {}); + BugReporting.setReportTypes(reportTypes); + } + + void addInvocationOption(InvocationOption invocationOption) { + if (invocationOptions.contains(invocationOption)) { + invocationOptions.remove(invocationOption); + } else { + invocationOptions.add(invocationOption); + } + BugReporting.setInvocationOptions(invocationOptions); + // BugReporting.setInvocationOptions([invocationOption]); + } + + void showDialogOnInvoke(BuildContext context) { + BugReporting.setOnDismissCallback((dismissType, reportType) { + if (dismissType == DismissType.submit) { + showDialog( + context: context, + builder: (_) => const AlertDialog( + title: Text('Bug Reporting sent'), + )); + } + }); + } + + void setOnDismissCallback(void Function(DismissType, ReportType) callback) { + BugReporting.setOnDismissCallback((dismissType, reportType) { + callback(dismissType, reportType); + }); + } + + void setOnInvoiceCallback(VoidCallback callback) { + BugReporting.setOnInvokeCallback(() { + callback.call(); + }); + } + + void setDisclaimerText() { + BugReporting.setDisclaimerText(disclaimerTextController.text); + } + + @override + void dispose() { + disclaimerTextController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + callbackHandlerProvider = context.read(); + + return Page( + title: 'Bug Reporting', + children: [ + const SectionTitle('Enabling Bug Reporting'), + InstabugButton( + symanticLabel: 'id_restart_instabug_btn', + key: const Key('instabug_restart'), + onPressed: restartInstabug, + text: 'Restart Instabug', + ), + InstabugButton( + symanticLabel: 'id_disable_instabug_btn', + key: const Key('instabug_disable'), + onPressed: () => Instabug.setEnabled(false), + text: "Disable Instabug", + ), + InstabugButton( + symanticLabel: 'id_enable_instabug_btn', + key: const Key('instabug_enable'), + onPressed: () => Instabug.setEnabled(true), + text: "Enable Instabug", + ), + InstabugButton( + symanticLabel: 'id_enable_instabug_btn', + key: const Key('instabug_post_sending_dialog'), + onPressed: () => {showDialogOnInvoke(context)}, + text: "Set the post sending dialog", + ), + const SectionTitle('Invocation events'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('invocation_event_none'), + onPressed: () => setInvocationEvent(InvocationEvent.none), + child: const Text('None'), + ).withSemanticsLabel('invocation_event_none'), + ElevatedButton( + key: const Key('invocation_event_shake'), + onPressed: () => setInvocationEvent(InvocationEvent.shake), + child: const Text('Shake'), + ).withSemanticsLabel('invocation_event_shake'), + ElevatedButton( + key: const Key('invocation_event_screenshot'), + onPressed: () => setInvocationEvent(InvocationEvent.screenshot), + child: const Text('Screenshot'), + ).withSemanticsLabel('invocation_event_screenshot'), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('invocation_event_floating'), + onPressed: () => + setInvocationEvent(InvocationEvent.floatingButton), + child: const Text('Floating Button'), + ).withSemanticsLabel('invocation_event_floating'), + ElevatedButton( + key: const Key('invocation_event_two_fingers'), + onPressed: () => + setInvocationEvent(InvocationEvent.twoFingersSwipeLeft), + child: const Text('Two Fingers Swipe Left'), + ).withSemanticsLabel('invocation_event_two_fingers'), + ], + ), + const SectionTitle('User Consent'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + layoutBehavior: ButtonBarLayoutBehavior.padded, + children: [ + ElevatedButton( + key: const Key('user_consent_media_manadatory'), + onPressed: () => setUserConsent( + 'media_mandatory', + "Mandatory for Media", + true, + true, + UserConsentActionType.dropAutoCapturedMedia), + child: const Text('Drop Media Mandatory'), + ).withSemanticsLabel('user_consent_media_manadatory'), + ElevatedButton( + key: const Key('user_consent_no_chat_manadatory'), + onPressed: () => setUserConsent( + 'noChat_mandatory', + "Mandatory for No Chat", + true, + true, + UserConsentActionType.noChat), + child: const Text('No Chat Mandatory'), + ).withSemanticsLabel('user_consent_no_chat_manadatory'), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + layoutBehavior: ButtonBarLayoutBehavior.padded, + children: [ + ElevatedButton( + key: const Key('user_consent_drop_logs_manadatory'), + onPressed: () => setUserConsent( + 'dropLogs_mandatory', + "Mandatory for Drop logs", + true, + true, + UserConsentActionType.dropLogs), + child: const Text('Drop logs Mandatory'), + ).withSemanticsLabel('user_consent_drop_logs_manadatory'), + ElevatedButton( + key: const Key('user_consent_no_chat_optional'), + onPressed: () => setUserConsent( + 'noChat_mandatory', + "Optional for No Chat", + false, + true, + UserConsentActionType.noChat), + child: const Text('No Chat optional'), + ).withSemanticsLabel('user_consent_no_chat_optional'), + ], + ), + const SectionTitle('Invocation Options'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('invocation_option_disable_post_sending_dialog'), + onPressed: () => addInvocationOption( + InvocationOption.disablePostSendingDialog), + child: const Text('disablePostSendingDialog'), + ).withSemanticsLabel( + 'invocation_option_disable_post_sending_dialog'), + ElevatedButton( + key: const Key('invocation_option_email_hidden'), + onPressed: () => + addInvocationOption(InvocationOption.emailFieldHidden), + child: const Text('emailFieldHidden'), + ).withSemanticsLabel('invocation_option_email_hidden'), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('invocation_option_comment_required'), + onPressed: () => + addInvocationOption(InvocationOption.commentFieldRequired), + child: const Text('commentFieldRequired'), + ).withSemanticsLabel('invocation_option_comment_required'), + ElevatedButton( + onPressed: () => + addInvocationOption(InvocationOption.emailFieldOptional), + child: const Text('emailFieldOptional'), + ).withSemanticsLabel('invocation_option_email_required'), + ], + ), + InstabugButton( + key: const Key('instabug_show'), + onPressed: show, + symanticLabel: 'instabug_show', + text: 'Invoke', + ), + const SectionTitle('Attachment Options'), + Wrap( + children: [ + CheckboxListTile( + isThreeLine: false, + tristate: false, + value: attachmentsOptionsScreenshot, + onChanged: (value) { + setState(() { + attachmentsOptionsScreenshot = value ?? false; + }); + addAttachmentOptions(); + }, + title: const Text("Screenshot"), + subtitle: const Text('Enable attachment for screenShot'), + key: const Key('attachment_option_screenshot'), + ).withSemanticsLabel('attachment_option_screenshot'), + CheckboxListTile( + value: attachmentsOptionsExtraScreenshot, + onChanged: (value) { + setState(() { + attachmentsOptionsExtraScreenshot = value ?? false; + }); + addAttachmentOptions(); + }, + title: const Text("Extra Screenshot"), + subtitle: const Text('Enable attachment for extra screenShot'), + key: const Key('attachment_option_extra_screenshot'), + ).withSemanticsLabel('attachment_option_extra_screenshot'), + CheckboxListTile( + value: attachmentsOptionsGalleryImage, + onChanged: (value) { + setState(() { + attachmentsOptionsGalleryImage = value ?? false; + }); + addAttachmentOptions(); + }, + title: const Text("Gallery"), + subtitle: const Text('Enable attachment for gallery'), + key: const Key('attachment_option_gallery'), + ).withSemanticsLabel('attachment_option_gallery'), + CheckboxListTile( + value: attachmentsOptionsScreenRecording, + onChanged: (value) { + setState(() { + attachmentsOptionsScreenRecording = value ?? false; + }); + addAttachmentOptions(); + }, + title: const Text("Screen Recording"), + subtitle: const Text('Enable attachment for screen Recording'), + key: const Key('attachment_option_screen_recording'), + ).withSemanticsLabel('attachment_option_screen_recording'), + ], + ), + const SectionTitle('Bug reporting type'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('bug_report_type_bug'), + style: ElevatedButton.styleFrom( + backgroundColor: reportTypes.contains(ReportType.bug) + ? Colors.grey.shade400 + : null), + onPressed: () => toggleReportType(ReportType.bug), + child: const Text('Bug'), + ).withSemanticsLabel('bug_report_type_bug'), + ElevatedButton( + key: const Key('bug_report_type_feedback'), + onPressed: () => toggleReportType(ReportType.feedback), + style: ElevatedButton.styleFrom( + backgroundColor: reportTypes.contains(ReportType.feedback) + ? Colors.grey.shade400 + : null), + child: const Text('Feedback'), + ).withSemanticsLabel('bug_report_type_feedback'), + ElevatedButton( + key: const Key('bug_report_type_question'), + onPressed: () => toggleReportType(ReportType.question), + style: ElevatedButton.styleFrom( + backgroundColor: reportTypes.contains(ReportType.question) + ? Colors.grey.shade400 + : null), + child: const Text('Question'), + ).withSemanticsLabel('bug_report_type_question'), + ], + ), + CheckboxListTile( + value: reportTypes.contains(ReportType.bug), + onChanged: (value) { + toggleReportType(ReportType.bug); + setState(() {}); + }, + title: const Text("Bug"), + subtitle: const Text('Enable Bug reporting type'), + key: const Key('bug_report_type_bug'), + ).withSemanticsLabel('bug_report_type_question'), + CheckboxListTile( + value: reportTypes.contains(ReportType.feedback), + onChanged: (value) { + toggleReportType(ReportType.feedback); + setState(() {}); + }, + title: const Text("Feedback"), + subtitle: const Text('Enable Feedback reporting type'), + key: const Key('bug_report_type_feedback'), + ).withSemanticsLabel('bug_report_type_question'), + CheckboxListTile( + value: reportTypes.contains(ReportType.question), + onChanged: (value) { + toggleReportType(ReportType.question); + setState(() {}); + }, + title: const Text("Question"), + subtitle: const Text('Enable Question reporting type'), + key: const Key('bug_report_type_question'), + ).withSemanticsLabel('bug_report_type_question'), + InstabugButton( + symanticLabel: 'id_send_bug', + onPressed: () => { + BugReporting.show( + ReportType.bug, [InvocationOption.emailFieldOptional]) + }, + text: 'Send Bug Report', + ), + const SectionTitle('Disclaimer Text'), + InstabugTextField( + key: const Key('disclaimer_text'), + controller: disclaimerTextController, + label: 'Enter disclaimer Text', + symanticLabel: 'id_disclaimer_input', + ), + ElevatedButton( + key: const Key('set_disclaimer_text'), + onPressed: () => setDisclaimerText, + child: const Text('set disclaimer text'), + ).withSemanticsLabel('set_disclaimer_text'), + const SectionTitle('Extended Bug Reporting'), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: const Key('extended_bug_report_mode_disabled'), + onPressed: () => BugReporting.setExtendedBugReportMode( + ExtendedBugReportMode.disabled), + child: const Text('disabled'), + ).withSemanticsLabel('extended_bug_report_mode_disabled'), + ElevatedButton( + key: + const Key('extended_bug_report_mode_required_fields_enabled'), + onPressed: () => BugReporting.setExtendedBugReportMode( + ExtendedBugReportMode.enabledWithRequiredFields), + child: const Text('enabledWithRequiredFields'), + ).withSemanticsLabel( + 'extended_bug_report_mode_required_fields_enabled'), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + alignment: MainAxisAlignment.start, + children: [ + ElevatedButton( + key: + const Key('extended_bug_report_mode_optional_fields_enabled'), + onPressed: () => BugReporting.setExtendedBugReportMode( + ExtendedBugReportMode.enabledWithOptionalFields), + child: const Text('enabledWithOptionalFields'), + ).withSemanticsLabel( + 'extended_bug_report_mode_optional_fields_enabled'), + ], + ), + const SectionTitle('Set Callback After Discarding'), + InstabugButton( + symanticLabel: 'enable_onDismiss_callback', + onPressed: () { + setOnDismissCallback((dismissType, reportType) { + callbackHandlerProvider?.addItem( + 'On Dismiss Handler', + Item(id: 'event - ${math.Random().nextInt(99999)}', fields: [ + KeyValuePair( + key: 'Date', value: DateTime.now().toIso8601String()) + ])); + + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('On Dismiss'), + content: Text( + 'onDismiss callback called with $dismissType and $reportType', + ), + ); + }, + ); + }); + }, + text: 'Enable Set On Dismiss Callback', + ), + InstabugButton( + symanticLabel: 'disable_onDismiss_callback', + onPressed: () { + setOnDismissCallback((dismissType, reportType) {}); + }, + text: 'Disable Set On Dismiss Callback', + ), + InstabugButton( + symanticLabel: 'crash_onDismiss_callback', + onPressed: () { + setOnDismissCallback((dismissType, reportType) { + callbackHandlerProvider?.addItem( + 'On Dismiss Handler', + Item(id: 'event - ${math.Random().nextInt(99999)}', fields: [ + KeyValuePair( + key: 'Date', value: DateTime.now().toIso8601String()) + ])); + throw Exception("Exception from setOnDismissCallback"); + }); + }, + text: 'Set On Dismiss Callback with Exception', + ), + InstabugButton( + symanticLabel: 'disable_onInvoice_callback', + onPressed: () { + setOnInvoiceCallback(() { + callbackHandlerProvider?.addItem( + 'Invoke Handler', + Item(id: 'event - ${math.Random().nextInt(99999)}', fields: [ + KeyValuePair( + key: 'Date', value: DateTime.now().toIso8601String()) + ])); + + showDialog( + context: context, + builder: (context) { + return const AlertDialog( + title: Text('On Invoice Callback'), + content: Text( + 'setOnInvoice callback called', + ), + ); + }, + ); + }); + }, + text: 'Set On Invoice Callback', + ), + InstabugButton( + onPressed: () { + setOnInvoiceCallback(() {}); + }, + text: 'Disable On Invoice Callback', + ), + InstabugButton( + symanticLabel: 'crash_onInvoice_callback', + onPressed: () { + setOnInvoiceCallback(() { + callbackHandlerProvider?.addItem( + 'Invoke Handler', + Item( + id: 'event - ${math.Random().nextInt(99999)}', + fields: [ + KeyValuePair( + key: 'Date', + value: DateTime.now().toIso8601String()) + ])); + throw Exception("Exception from setOnInvoiceCallback"); + }); + }, + text: 'Set On Invoice Callback with Exception'), + const SectionTitle('Attachments'), + if (fileAttachment != null) + Text(fileAttachment!.path.split('/').last.substring(0) + " Attached"), + InstabugButton( + onPressed: addFileAttachment, + text: 'Add file attachment', + symanticLabel: 'add_file_attachment', + ), + InstabugButton( + onPressed: removeFileAttachment, + text: 'Clear All attachment', + symanticLabel: 'clear_file_attachment', + ), + ], // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/example/lib/src/screens/callback/callback_handler_provider.dart b/example/lib/src/screens/callback/callback_handler_provider.dart new file mode 100644 index 000000000..59a06468e --- /dev/null +++ b/example/lib/src/screens/callback/callback_handler_provider.dart @@ -0,0 +1,32 @@ +import 'package:flutter/foundation.dart'; + +class KeyValuePair { + final String key; + final String value; + + KeyValuePair({required this.key, required this.value}); +} + +class Item { + final String id; + final List fields; + + Item({required this.id, required this.fields}); +} + +class CallbackHandlersProvider extends ChangeNotifier { + final Map> _callbackHandlers = {}; + + Map> get callbackHandlers => _callbackHandlers; + + void clearList(String title) { + _callbackHandlers[title] = []; + notifyListeners(); + } + + void addItem(String title, Item item) { + final existingList = _callbackHandlers[title] ?? []; + _callbackHandlers[title] = [...existingList, item]; + notifyListeners(); + } +} diff --git a/example/lib/src/screens/callback/callback_page.dart b/example/lib/src/screens/callback/callback_page.dart new file mode 100644 index 000000000..d0ff5bdb6 --- /dev/null +++ b/example/lib/src/screens/callback/callback_page.dart @@ -0,0 +1,110 @@ +// callback_screen.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'callback_handler_provider.dart'; + +class CallbackScreen extends StatelessWidget { + static var screenName = "/callback"; + + const CallbackScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final handlers = context.watch().callbackHandlers; + final titles = handlers.keys.toList(); + + return DefaultTabController( + length: titles.isEmpty ? 1 : titles.length, + child: Scaffold( + appBar: AppBar( + title: const Text("Callback Handlers"), + bottom: TabBar( + isScrollable: true, + tabs: titles.isEmpty + ? [const Tab(text: "No Data")] + : titles.map((t) => Tab(text: t)).toList(), + ), + ), + body: TabBarView( + children: titles.isEmpty + ? [ + const Center(child: Text("No callback handlers yet")), + ] + : titles.map((title) { + return CallBackTabScreen(title: title); + }).toList(), + ), + ), + ); + } +} + +class CallBackTabScreen extends StatelessWidget { + final String title; + const CallBackTabScreen({Key? key, required this.title}) : super(key: key); + + @override + Widget build(BuildContext context) { + final provider = context.watch(); + final items = provider.callbackHandlers[title] ?? []; + + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Items: ${items.length}", + style: const TextStyle(fontWeight: FontWeight.bold)), + ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), + onPressed: () => provider.clearList(title), + child: const Text("Clear Data", + style: TextStyle(color: Colors.white)), + ), + ], + ), + const SizedBox(height: 10), + Expanded( + child: items.isEmpty + ? const Center(child: Text("No items")) + : ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return Card( + margin: const EdgeInsets.only(bottom: 8), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: item.fields.map((field) { + return Padding( + padding: + const EdgeInsets.symmetric(vertical: 2), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text(field.key, + style: const TextStyle( + fontWeight: FontWeight.bold)), + Text(field.value, + style: const TextStyle( + color: Colors.grey)), + ], + ), + ); + }).toList(), + ), + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/src/screens/complex_page.dart b/example/lib/src/screens/complex_page.dart index 65fdd8a57..059f9b7a6 100644 --- a/example/lib/src/screens/complex_page.dart +++ b/example/lib/src/screens/complex_page.dart @@ -75,11 +75,13 @@ class _ComplexPageState extends State { label: 'Depth (default: ${ComplexPage.initialDepth})', labelStyle: textTheme.labelMedium, controller: depthController, + symanticLabel: 'depth_input', ), InstabugTextField( label: 'Breadth (default: ${ComplexPage.initialBreadth})', labelStyle: textTheme.labelMedium, controller: breadthController, + symanticLabel: 'breadth_input', ), InstabugButton( onPressed: _handleRender, @@ -93,22 +95,27 @@ class _ComplexPageState extends State { InstabugButton( onPressed: _enableScreenLoading, text: 'Enable Screen loading', + symanticLabel: 'enable_screen_loading', ), InstabugButton( onPressed: _disableScreenLoading, text: 'Disable Screen Loading', + symanticLabel: 'disable_screen_loading', ), InstabugButton( onPressed: _resetDidStartScreenLoading, text: 'Reset Did Start Screen Loading', + symanticLabel: 'reset_start_screen_loading', ), InstabugButton( onPressed: _resetDidReportScreenLoading, text: 'Reset Did Report Screen Loading', + symanticLabel: 'reset_report_screen_loading', ), InstabugButton( onPressed: _resetDidExtendScreenLoading, text: 'Reset Did Extend Screen Loading', + symanticLabel: 'reset_extend_screen_loading', ), SingleChildScrollView( scrollDirection: Axis.horizontal, diff --git a/example/lib/src/screens/my_home_page.dart b/example/lib/src/screens/my_home_page.dart index 5f7d50a88..d9f1a9f99 100644 --- a/example/lib/src/screens/my_home_page.dart +++ b/example/lib/src/screens/my_home_page.dart @@ -20,6 +20,9 @@ class _MyHomePageState extends State { final primaryColorController = TextEditingController(); final screenNameController = TextEditingController(); final featureFlagsController = TextEditingController(); + final userAttributeKeyController = TextEditingController(); + + final userAttributeValueController = TextEditingController(); @override void dispose() { @@ -124,6 +127,11 @@ class _MyHomePageState extends State { Instabug.setColorTheme(colorTheme); } + void _navigateToBugs() { + ///This way of navigation utilize screenLoading automatic approach [Navigator 1] + Navigator.pushNamed(context, BugReportingPage.screenName); + } + void _navigateToCrashes() { ///This way of navigation utilize screenLoading automatic approach [Navigator 1] Navigator.pushNamed(context, CrashesPage.screenName); @@ -161,6 +169,28 @@ class _MyHomePageState extends State { ); } + void _navigateToCallbackHandler() { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CallbackScreen(), + settings: RouteSettings(name: CallbackScreen.screenName), + ), + ); + } + + void _navigateToSessionReplay() { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const SessionReplayPage(), + settings: const RouteSettings(name: SessionReplayPage.screenName), + ), + ); + } + + final _formUserAttributeKey = GlobalKey(); + @override Widget build(BuildContext context) { return Page( @@ -177,15 +207,28 @@ class _MyHomePageState extends State { InstabugButton( onPressed: restartInstabug, text: 'Restart Instabug', + symanticLabel: 'restart_page', + ), + InstabugButton( + onPressed: _navigateToBugs, + text: 'Bug Reporting', + symanticLabel: 'open_bug_reporting', + ), + InstabugButton( + onPressed: _navigateToCallbackHandler, + text: 'Callback Page', + symanticLabel: 'open_callback_page', ), const SectionTitle('Primary Color'), InstabugTextField( controller: primaryColorController, label: 'Enter primary color', + symanticLabel: 'primary_color_input', ), InstabugButton( text: 'Change Primary Color', onPressed: changePrimaryColor, + symanticLabel: 'set_primary_color', ), const SectionTitle('Change Invocation Event'), ButtonBar( @@ -196,17 +239,17 @@ class _MyHomePageState extends State { onPressed: () => setInvocationEvent(InvocationEvent.none), style: buttonStyle, child: const Text('None'), - ), + ).withSemanticsLabel('invocation_event_none'), ElevatedButton( onPressed: () => setInvocationEvent(InvocationEvent.shake), style: buttonStyle, child: const Text('Shake'), - ), + ).withSemanticsLabel('invocation_event_shake'), ElevatedButton( onPressed: () => setInvocationEvent(InvocationEvent.screenshot), style: buttonStyle, child: const Text('Screenshot'), - ), + ).withSemanticsLabel('invocation_event_screenshot'), ], ), ButtonBar( @@ -218,13 +261,13 @@ class _MyHomePageState extends State { setInvocationEvent(InvocationEvent.floatingButton), style: buttonStyle, child: const Text('Floating Button'), - ), + ).withSemanticsLabel('invocation_event_floating_button'), ElevatedButton( onPressed: () => setInvocationEvent(InvocationEvent.twoFingersSwipeLeft), style: buttonStyle, child: const Text('Two Fingers Swipe Left'), - ), + ).withSemanticsLabel('invocation_event_two_fingers'), ], ), InstabugButton( @@ -239,18 +282,22 @@ class _MyHomePageState extends State { InstabugTextField( controller: screenNameController, label: 'Enter screen name', + symanticLabel: 'screen_name_input', ), InstabugButton( text: 'Report Screen Change', onPressed: reportScreenChange, + symanticLabel: 'set_screen_name', ), InstabugButton( onPressed: sendBugReport, text: 'Send Bug Report', + symanticLabel: 'send_bug_report', ), InstabugButton( onPressed: showManualSurvey, text: 'Show Manual Survey', + symanticLabel: 'show_manual_survery', ), const SectionTitle('Change Report Types'), ButtonBar( @@ -261,55 +308,69 @@ class _MyHomePageState extends State { onPressed: () => toggleReportType(ReportType.bug), style: buttonStyle, child: const Text('Bug'), - ), + ).withSemanticsLabel('bug_report_type_bug'), ElevatedButton( onPressed: () => toggleReportType(ReportType.feedback), style: buttonStyle, child: const Text('Feedback'), - ), + ).withSemanticsLabel('bug_report_type_feedback'), ElevatedButton( onPressed: () => toggleReportType(ReportType.question), style: buttonStyle, child: const Text('Question'), - ), + ).withSemanticsLabel('bug_report_type_question'), ], ), InstabugButton( onPressed: changeFloatingButtonEdge, text: 'Move Floating Button to Left', + symanticLabel: 'move_floating_button_to_left', ), InstabugButton( onPressed: sendFeedback, text: 'Send Feedback', + symanticLabel: 'sending_feedback', ), InstabugButton( onPressed: showNpsSurvey, text: 'Show NPS Survey', + symanticLabel: 'show_nps_survey', ), InstabugButton( onPressed: showManualSurvey, text: 'Show Multiple Questions Survey', + symanticLabel: 'show_multi_question_survey', ), InstabugButton( onPressed: showFeatureRequests, text: 'Show Feature Requests', + symanticLabel: 'show_feature_requests', ), InstabugButton( onPressed: _navigateToCrashes, text: 'Crashes', + symanticLabel: 'open_crash_page', ), InstabugButton( onPressed: _navigateToApm, text: 'APM', + symanticLabel: 'open_apm_page', ), InstabugButton( onPressed: _navigateToComplex, text: 'Complex', + symanticLabel: 'open_complex_page', + ), + InstabugButton( + onPressed: _navigateToSessionReplay, + text: 'Session Replay', + symanticLabel: 'open_session_replay_page', ), const SectionTitle('Sessions Replay'), InstabugButton( onPressed: getCurrentSessionReplaylink, text: 'Get current session replay link', + symanticLabel: 'get_current_session_replay_link', ), const SectionTitle('Color Theme'), ButtonBar( @@ -319,37 +380,185 @@ class _MyHomePageState extends State { ElevatedButton( onPressed: () => setColorTheme(ColorTheme.light), style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Colors.white), - foregroundColor: MaterialStateProperty.all(Colors.lightBlue), + backgroundColor: WidgetStateProperty.all(Colors.white), + foregroundColor: WidgetStateProperty.all(Colors.lightBlue), ), - child: const Text('Light'), - ), + child: const Text('set_color_theme_light'), + ).withSemanticsLabel(''), ElevatedButton( onPressed: () => setColorTheme(ColorTheme.dark), style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Colors.black), - foregroundColor: MaterialStateProperty.all(Colors.white), + backgroundColor: WidgetStateProperty.all(Colors.black), + foregroundColor: WidgetStateProperty.all(Colors.white), ), - child: const Text('Dark'), - ), + child: const Text('set_color_theme_dark'), + ).withSemanticsLabel(''), ], ), - SectionTitle('FeatureFlags'), + const SectionTitle('FeatureFlags'), InstabugTextField( controller: featureFlagsController, label: 'Feature Flag name', + symanticLabel: 'feature_flag_name_input', ), InstabugButton( onPressed: () => setFeatureFlag(), text: 'SetFeatureFlag', + symanticLabel: 'set_feature_flag', ), InstabugButton( onPressed: () => removeFeatureFlag(), text: 'RemoveFeatureFlag', + symanticLabel: 'remove_feature_flag', ), InstabugButton( onPressed: () => removeAllFeatureFlags(), text: 'RemoveAllFeatureFlags', + symanticLabel: 'remove_all_feature_flags', + ), + const SectionTitle('Set User Attribute'), + Form( + key: _formUserAttributeKey, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: InstabugTextField( + label: "User Attribute key", + symanticLabel: 'user_attribute_key_input', + key: const Key("user_attribute_key_textfield"), + controller: userAttributeKeyController, + validator: (value) { + if (value?.trim().isNotEmpty == true) return null; + return 'this field is required'; + }, + )), + Expanded( + child: InstabugTextField( + label: "User Attribute Value", + symanticLabel: 'user_attribute_value_input', + key: const Key("user_attribute_value_textfield"), + controller: userAttributeValueController, + validator: (value) { + if (value?.trim().isNotEmpty == true) return null; + + return 'this field is required'; + }, + )), + ], + ), + const SizedBox( + height: 8, + ), + InstabugButton( + text: 'Set User attribute', + symanticLabel: 'set_user_attribute', + key: const Key('set_user_data_btn'), + onPressed: () { + if (_formUserAttributeKey.currentState?.validate() == true) { + Instabug.setUserAttribute(userAttributeKeyController.text, + userAttributeValueController.text); + } + }, + ), + InstabugButton( + text: 'remove User attribute', + symanticLabel: 'remove_user_attribute', + key: const Key('remove_user_data_btn'), + onPressed: () { + if (_formUserAttributeKey.currentState?.validate() == true) { + Instabug.removeUserAttribute( + userAttributeKeyController.text); + } + }, + ), + const SizedBox( + height: 10, + ), + const SectionTitle('Log'), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + children: [ + Expanded( + child: InstabugButton( + symanticLabel: 'log_hello_debug', + margin: const EdgeInsets.symmetric(horizontal: 2), + key: const ValueKey('log_hello_debug_btn'), + onPressed: () { + InstabugLog.logDebug("hello Debug"); + }, + text: 'Log Hello Debug', + ), + ), + Expanded( + child: InstabugButton( + symanticLabel: 'log_hello_error', + margin: const EdgeInsets.symmetric(horizontal: 2), + key: const ValueKey('log_hello_error_btn'), + onPressed: () { + InstabugLog.logError("hello Error"); + }, + text: 'Log Hello Error', + ), + ), + Expanded( + child: InstabugButton( + symanticLabel: 'log_hello_warning', + margin: const EdgeInsets.symmetric(horizontal: 2), + key: const ValueKey('hello_warning_btn'), + onPressed: () { + InstabugLog.logWarn("hello Warning"); + }, + text: 'Log Hello Warn', + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + children: [ + Expanded( + child: InstabugButton( + symanticLabel: 'log_hello_info_btn', + margin: const EdgeInsets.symmetric(horizontal: 2), + key: const ValueKey('log_hello_info_btn'), + onPressed: () { + InstabugLog.logInfo("hello Info"); + }, + text: 'Log Hello Info', + ), + ), + Expanded( + child: InstabugButton( + symanticLabel: 'log_hello_verbose_btn', + margin: const EdgeInsets.symmetric(horizontal: 2), + key: const ValueKey('log_hello_verbose_btn'), + onPressed: () { + InstabugLog.logVerbose("hello Verbose"); + }, + text: 'Log Hello Verbose', + ), + ), + Expanded( + child: InstabugButton( + symanticLabel: 'clear_logs', + margin: const EdgeInsets.symmetric(horizontal: 2), + key: const ValueKey('clear_logs_btn'), + onPressed: () { + InstabugLog.clearAllLogs(); + }, + text: 'Clear All logs', + ), + ), + ], + ), + ), + ], + ), ), ], ); diff --git a/example/lib/src/screens/screen_loading_page.dart b/example/lib/src/screens/screen_loading_page.dart index a2b49e681..cf589dd0f 100644 --- a/example/lib/src/screens/screen_loading_page.dart +++ b/example/lib/src/screens/screen_loading_page.dart @@ -132,6 +132,7 @@ class _ScreenLoadingPageState extends State { label: 'Duration', controller: durationController, keyboardType: TextInputType.number, + symanticLabel: 'duration_input', ), Container( margin: const EdgeInsets.only(top: 12), @@ -142,20 +143,24 @@ class _ScreenLoadingPageState extends State { InstabugButton( text: 'Extend Screen Loading (Testing)', onPressed: _extendScreenLoadingTestingEnvironment, + symanticLabel: 'extend_screen_loading_testing', ), InstabugButton( text: 'Extend Screen Loading (Production)', onPressed: _extendScreenLoading, + symanticLabel: 'extend_screen_loading_production', ), ], )), InstabugButton( text: 'Monitored Complex Page', onPressed: _navigateToComplexPage, + symanticLabel: 'monitored_complex_page', ), InstabugButton( text: 'Screen Capture Premature Extension Page', onPressed: _navigateToMonitoredScreenCapturePrematureExtensionPage, + symanticLabel: 'screen_capture_premature_extension_page', ), SectionTitle('Dynamic Screen Loading list'), SizedBox( diff --git a/example/lib/src/screens/session_replay_page.dart b/example/lib/src/screens/session_replay_page.dart new file mode 100644 index 000000000..534c41fd6 --- /dev/null +++ b/example/lib/src/screens/session_replay_page.dart @@ -0,0 +1,125 @@ +part of '../../main.dart'; + +class SessionReplayPage extends StatefulWidget { + static const screenName = 'SessionReplay'; + + const SessionReplayPage({Key? key}) : super(key: key); + + @override + State createState() => _SessionReplayPageState(); +} + +class _SessionReplayPageState extends State { + @override + Widget build(BuildContext context) { + return Page(title: 'Session Replay', children: [ + const SectionTitle('Enabling Session Replay'), + InstabugButton( + key: const Key('instabug_sesssion_replay_disable'), + onPressed: () => SessionReplay.setEnabled(false), + text: "Disable Session Replay", + symanticLabel: 'instabug_sesssion_replay_disable', + ), + InstabugButton( + key: const Key('instabug_sesssion_replay_enable'), + onPressed: () => SessionReplay.setEnabled(true), + text: "Enable Session Replay", + symanticLabel: 'instabug_sesssion_replay_enable', + ), + const SectionTitle('Enabling Session Replay Network'), + InstabugButton( + key: const Key('instabug_sesssion_replay_network_disable'), + onPressed: () => SessionReplay.setNetworkLogsEnabled(false), + text: "Disable Session Replay Network", + symanticLabel: 'instabug_sesssion_replay_network_disable', + ), + InstabugButton( + key: const Key('instabug_sesssion_replay_network_enable'), + onPressed: () => SessionReplay.setNetworkLogsEnabled(true), + text: "Enable Session Replay Network", + symanticLabel: 'instabug_sesssion_replay_network_enable', + ), + const SectionTitle('Enabling Session Replay User Steps'), + InstabugButton( + key: const Key('instabug_sesssion_replay_user_steps_disable'), + onPressed: () => SessionReplay.setUserStepsEnabled(false), + text: "Disable Session Replay User Steps", + symanticLabel: 'instabug_sesssion_replay_user_steps_disable', + ), + InstabugButton( + key: const Key('instabug_sesssion_replay_user_steps_enable'), + onPressed: () => SessionReplay.setUserStepsEnabled(true), + text: "Enable Session Replay User Steps", + symanticLabel: 'instabug_sesssion_replay_user_steps_enable', + ), + const SectionTitle('Enabling Session Replay Logs'), + InstabugButton( + key: const Key('instabug_sesssion_replay_logs_disable'), + onPressed: () => SessionReplay.setInstabugLogsEnabled(false), + text: "Disable Session Replay Logs", + symanticLabel: 'instabug_sesssion_replay_logs_disable', + ), + InstabugButton( + key: const Key('instabug_sesssion_replay_logs_enable'), + onPressed: () => SessionReplay.setInstabugLogsEnabled(true), + text: "Enable Session Replay Logs", + symanticLabel: 'instabug_sesssion_replay_logs_enable', + ), + const SectionTitle('Enabling Session Replay Repro steps'), + InstabugButton( + key: const Key('instabug_sesssion_replay_repro_steps_disable'), + onPressed: () => Instabug.setReproStepsConfig( + sessionReplay: ReproStepsMode.disabled), + text: "Disable Session Replay Repro steps", + symanticLabel: 'instabug_sesssion_replay_repro_steps_disable', + ), + InstabugButton( + key: const Key('instabug_sesssion_replay_repro_steps_enable'), + onPressed: () => + Instabug.setReproStepsConfig(sessionReplay: ReproStepsMode.enabled), + text: "Enable Session Replay Repro steps", + symanticLabel: 'instabug_sesssion_replay_repro_steps_enable', + ), + InstabugButton( + key: const Key('instabug_sesssion_replay_tab_screen'), + onPressed: () => Navigator.of(context).pushNamed(TopTabBarScreen.route), + text: 'Open Tab Screen', + symanticLabel: 'instabug_sesssion_replay_tab_screen', + ), + ]); + } +} + +class TopTabBarScreen extends StatelessWidget { + static const String route = "/tap"; + + const TopTabBarScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 4, // Number of tabs + child: Scaffold( + appBar: AppBar( + title: const Text('Top TabBar with 4 Tabs'), + bottom: const TabBar( + tabs: [ + Tab(text: 'Home', icon: Icon(Icons.home)), + Tab(text: 'Search', icon: Icon(Icons.search)), + Tab(text: 'Alerts', icon: Icon(Icons.notifications)), + Tab(text: 'Profile', icon: Icon(Icons.person)), + ], + ), + ), + body: const TabBarView( + children: [ + Center(child: Text('Home Screen')), + Center(child: Text('Search Screen')), + Center(child: Text('Alerts Screen')), + Center(child: Text('Profile Screen')), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/utils/widget_ext.dart b/example/lib/src/utils/widget_ext.dart new file mode 100644 index 000000000..504c9bcb2 --- /dev/null +++ b/example/lib/src/utils/widget_ext.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +extension WidgetExt on Widget { + Widget withSemanticsLabel(String label) { + return Semantics( + label: label, + child: this, + ); + } +} diff --git a/example/lib/src/widget/instabug_button.dart b/example/lib/src/widget/instabug_button.dart index 97e434061..32477b1a7 100644 --- a/example/lib/src/widget/instabug_button.dart +++ b/example/lib/src/widget/instabug_button.dart @@ -1,28 +1,36 @@ import 'package:flutter/material.dart'; class InstabugButton extends StatelessWidget { - const InstabugButton({ - Key? key, - required this.text, - this.onPressed, - this.fontSize, - this.margin, - }) : super(key: key); + InstabugButton( + {Key? key, + required this.text, + this.onPressed, + this.fontSize, + this.margin, + this.backgroundColor, + this.symanticLabel}) + : super( + key: + key ?? (symanticLabel != null ? ValueKey(symanticLabel) : key)); - const InstabugButton.smallFontSize({ - Key? key, - required this.text, - this.onPressed, - this.fontSize = 10.0, - this.margin, - }) : super(key: key); + const InstabugButton.smallFontSize( + {Key? key, + required this.text, + this.onPressed, + this.fontSize = 10.0, + this.margin, + this.backgroundColor, + this.symanticLabel}) + : super(key: key); final String text; final Function()? onPressed; final double? fontSize; - + final Color? backgroundColor; final EdgeInsetsGeometry? margin; + final String? symanticLabel; + @override Widget build(BuildContext context) { return Container( @@ -31,17 +39,22 @@ class InstabugButton extends StatelessWidget { const EdgeInsets.symmetric( horizontal: 20.0, ), - child: ElevatedButton( - onPressed: onPressed, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.lightBlue, - foregroundColor: Colors.white, - textStyle: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith(fontSize: fontSize), + child: Semantics( + label: symanticLabel, + child: ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: backgroundColor ?? Colors.lightBlue, + foregroundColor: Colors.white, + textStyle: Theme.of(context).textTheme.labelLarge?.copyWith( + fontSize: fontSize, + ), + ), + child: Text( + text, + textAlign: TextAlign.center, + ), ), - child: Text(text), ), ); } diff --git a/example/lib/src/widget/instabug_clipboard_input.dart b/example/lib/src/widget/instabug_clipboard_input.dart index 584faea81..09799b355 100644 --- a/example/lib/src/widget/instabug_clipboard_input.dart +++ b/example/lib/src/widget/instabug_clipboard_input.dart @@ -3,14 +3,16 @@ import 'package:instabug_flutter_example/src/widget/instabug_text_field.dart'; import 'package:instabug_flutter_example/src/widget/instabug_clipboard_icon_button.dart'; class InstabugClipboardInput extends StatelessWidget { - const InstabugClipboardInput({ - Key? key, - required this.label, - required this.controller, - }) : super(key: key); + const InstabugClipboardInput( + {Key? key, + required this.label, + required this.controller, + this.symanticLabel}) + : super(key: key); final String label; final TextEditingController controller; + final String? symanticLabel; @override Widget build(BuildContext context) { @@ -18,12 +20,12 @@ class InstabugClipboardInput extends StatelessWidget { children: [ Expanded( child: InstabugTextField( - label: label, - margin: const EdgeInsetsDirectional.only( - start: 20.0, - ), - controller: controller, - ), + label: label, + margin: const EdgeInsetsDirectional.only( + start: 20.0, + ), + controller: controller, + symanticLabel: symanticLabel), ), InstabugClipboardIconButton( onPaste: (String? clipboardText) { diff --git a/example/lib/src/widget/instabug_text_field.dart b/example/lib/src/widget/instabug_text_field.dart index 3d01cc623..1e3c23870 100644 --- a/example/lib/src/widget/instabug_text_field.dart +++ b/example/lib/src/widget/instabug_text_field.dart @@ -1,15 +1,16 @@ import 'package:flutter/material.dart'; class InstabugTextField extends StatelessWidget { - const InstabugTextField({ - Key? key, - required this.label, - required this.controller, - this.labelStyle, - this.margin, - this.keyboardType, - this.validator, - }) : super(key: key); + const InstabugTextField( + {Key? key, + required this.label, + required this.controller, + this.labelStyle, + this.margin, + this.keyboardType, + this.validator, + this.symanticLabel}) + : super(key: key); final String label; final TextEditingController controller; @@ -17,6 +18,7 @@ class InstabugTextField extends StatelessWidget { final TextStyle? labelStyle; final TextInputType? keyboardType; final FormFieldValidator? validator; + final String? symanticLabel; @override Widget build(BuildContext context) { @@ -25,18 +27,21 @@ class InstabugTextField extends StatelessWidget { const EdgeInsets.symmetric( horizontal: 20.0, ), - child: TextFormField( - controller: controller, - keyboardType: keyboardType, - validator: validator, - decoration: InputDecoration( - labelText: label, - labelStyle: labelStyle ?? Theme.of(context).textTheme.labelLarge, - suffixIcon: IconButton( - onPressed: controller.clear, - iconSize: 12.0, - icon: const Icon( - Icons.clear, + child: Semantics( + label: label, + child: TextFormField( + controller: controller, + keyboardType: keyboardType, + validator: validator, + decoration: InputDecoration( + labelText: label, + labelStyle: labelStyle ?? Theme.of(context).textTheme.labelLarge, + suffixIcon: IconButton( + onPressed: controller.clear, + iconSize: 12.0, + icon: const Icon( + Icons.clear, + ), ), ), ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 99e8f9d56..7dcb19811 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" fake_async: dependency: transitive description: @@ -49,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" file: dependency: transitive description: @@ -57,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: c904b4ab56d53385563c7c39d8e9fa9af086f91495dfc48717ad84a42c3cf204 + url: "https://pub.dev" + source: hosted + version: "8.1.7" flutter: dependency: "direct main" description: flutter @@ -75,11 +99,24 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab" + url: "https://pub.dev" + source: hosted + version: "2.0.29" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -172,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -188,6 +233,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" process: dependency: transitive description: @@ -196,6 +249,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.2" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" sky_engine: dependency: transitive description: flutter @@ -281,6 +342,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" webdriver: dependency: transitive description: @@ -289,6 +358,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.4" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" sdks: - dart: ">=3.5.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index dfd49f2aa..4d28d3a50 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -27,6 +27,8 @@ dependencies: instabug_flutter: path: ../ instabug_http_client: ^2.4.0 + file_picker: <10.0.0 + provider: dev_dependencies: flutter_driver: