Skip to content

Commit

Permalink
chore: Internal updates (#43)
Browse files Browse the repository at this point in the history
fix(TM-47414): Setting the value of the text box on submit and closing the dialog box
feat(TM-48504): Creating custom widget having copying capability
feat(TM-48272): Widget locator updated for quill editor
test(TM-48505): Widget Test case for custom widget having copying capability
ci: Update PR action
  • Loading branch information
thelukewalton authored Nov 22, 2024
1 parent 30395b0 commit 7fc6bef
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 4 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ jobs:
- uses: ZebraDevs/flutter-code-quality@main
with:
token: ${{secrets.GITHUB_TOKEN}}
run-tests: false
run-coverage: false

check-secret:
Expand All @@ -34,7 +33,7 @@ jobs:
id: check
run: |
echo "defined=true" >> $GITHUB_OUTPUT;
if [ "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ZETA_DS }}" == '' ]; then
if [ "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ZDS_FLUTTER }}" == '' ]; then
echo "defined=false" >> $GITHUB_OUTPUT;
fi
Expand Down
56 changes: 56 additions & 0 deletions example/lib/pages/components/selectable_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:zds_flutter/zds_flutter.dart';

/// Contains a demonstration of the ZdsSelectableWidget class.
/// This demo showcases how to use the ZdsSelectableWidget with both HTML and plain text content within a Flutter application.
class SelectableWidgetDemo extends StatelessWidget {
const SelectableWidgetDemo({super.key});
@override
Widget build(BuildContext context) {
String htmlContent =
'''<p style="margin-left:0px;"><br><span style="color:#202122;font-family:'Arial',sans-serif;font-size:10.5pt;"><strong>Birds</strong> are a group of </span><p target="_blank" rel="noopener noreferrer\" href="https://en.wikipedia.org/wiki/Warm-blooded"><span style="color:#3366CC;font-family:'Arial',sans-serif;font-size:10.5pt;">warm-blooded</span></a>
This paragraph contains a lot of lines in the source code, but the browser ignores it.
This paragraph contains a lot of spaces in the source code, but the browser ignores it.
The number of lines in a paragraph depends on the size of the browser window. If you resize the browser window, the number of lines in this paragraph will change.</p>
''';
String plainTextContent =
'The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.';
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: Column(
children: [
ZdsListGroup(
padding: EdgeInsets.all(10.0),
headerLabel: Text('For Html Content'),
items: [
ZdsSelectableWidget(
copyable: true,
textToCopy: htmlContent,
isHtmlData: true,
child: ZdsHtmlContainer(
htmlContent,
showReadMore: false,
onLinkTap: (_, __, ___) {
print('Link tapped');
},
),
)
],
),
ZdsListGroup(
padding: EdgeInsets.all(10.0),
headerLabel: Text('For Plain Text Content'),
items: [
ZdsSelectableWidget(
copyable: true,
textToCopy: plainTextContent,
isHtmlData: true,
child: Text('$plainTextContent'),
)
],
),
],
),
);
}
}
5 changes: 5 additions & 0 deletions example/lib/routes.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:zds_flutter_example/pages/components/chat.dart';
import 'package:zds_flutter_example/pages/components/comment.dart';
import 'package:zds_flutter_example/pages/components/selectable_widget.dart';

import 'home.dart';
import 'pages/assets/animations.dart';
Expand Down Expand Up @@ -318,6 +319,10 @@ final kRoutes = {
title: 'Nested Flow',
child: NestedFlowDemo(),
),
const DemoRoute(
title: 'SelectableWidget',
child: SelectableWidgetDemo(),
),
],
'Assets': [
const DemoRoute(
Expand Down
1 change: 1 addition & 0 deletions lib/src/components/organisms.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export 'organisms/properties_list.dart';
export 'organisms/quill_editor/quill.dart';
export 'organisms/radio_list.dart';
export 'organisms/search_app_bar.dart';
export 'organisms/selectable_widget.dart';
export 'organisms/tab_scaffold.dart';
export 'organisms/tags_list.dart';
export 'organisms/temp_directory/resolver.dart' show clearUiTempDirectory;
9 changes: 8 additions & 1 deletion lib/src/components/organisms/file_picker/file_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,14 @@ class _MultiInputDialogState extends State<_MultiInputDialog> {
widget.textFields[index].value = value;
});
},
onFieldSubmitted: (String value) => widget.textFields[index].value = value,
onFieldSubmitted: (String value) {
setState(() {
widget.textFields[index].value = value;
});
if (isValid) {
unawaited(Navigator.maybePop(context, widget.textFields));
}
},
validator: (String? value) => widget.onValidate?.call(widget.textFields[index]),
decoration: ZdsInputDecoration(
hintText: widget.textFields[index].hint,
Expand Down
3 changes: 2 additions & 1 deletion lib/src/components/organisms/quill_editor/quill_editor.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import '../../../../zds_flutter.dart';
import 'quill_toolbar.dart';

/// A custom widget for the Quill editor.
Expand Down Expand Up @@ -91,7 +92,7 @@ class ZdsQuillEditor extends StatelessWidget {
placeholder: placeholder,
editorKey: editorKey,
),
);
).semantics(identifier: 'TEXT_EDITOR');

// If readOnly, return just editor
if (readOnly) return editor;
Expand Down
112 changes: 112 additions & 0 deletions lib/src/components/organisms/selectable_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as html_parser;

import '../../../zds_flutter.dart';

/// A selectable widget that can be used to select the text from the child content on long press.
///
/// Contains the implementation of the ZdsSelectableWidget class.
/// This widget allows users to select and copy text from its child content on a long press.
/// It supports both plain text and HTML content, converting HTML to plain text if necessary
class ZdsSelectableWidget extends StatefulWidget {
/// Constructor
const ZdsSelectableWidget({super.key, required this.child, required this.textToCopy, this.isHtmlData, this.copyable});

/// Child widget
final Widget child;

/// text to be copied
final String textToCopy;

/// Whether the copied text is in HTML format (if it is we will convert it to plain text)
final bool? isHtmlData;

/// Whether the copied text is copyable
final bool? copyable;

@override
State<ZdsSelectableWidget> createState() => _ZdsSelectableWidgetState();

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<bool?>('isHtmlData', isHtmlData))
..add(DiagnosticsProperty<bool>('copyable', copyable))
..add(StringProperty('textToCopy', textToCopy));
}
}

class _ZdsSelectableWidgetState extends State<ZdsSelectableWidget> {
bool isSelected = false;
Timer? _selectionTimer;

void toggleSelection() {
setState(() {
isSelected = !isSelected;
});
}

String htmlToPlainText(String htmlString) {
final dom.Document document = html_parser.parse(htmlString);
return document.body?.text ?? '';
}

@override
void dispose() {
_selectionTimer?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
if (!(widget.copyable ?? false)) return widget.child;
final zeta = Zeta.of(context).colors;
return GestureDetector(
child: ColoredBox(color: isSelected ? zeta.primary.surface : Colors.transparent, child: widget.child),
onLongPress: () async {
if (isSelected) return;
toggleSelection();
if (isSelected) {
var copiedText = widget.textToCopy;
if (widget.isHtmlData ?? false) {
try {
copiedText = htmlToPlainText(widget.textToCopy);
} catch (e) {
copiedText = widget.textToCopy;
}
}
await Clipboard.setData(ClipboardData(text: copiedText));
ScaffoldMessenger.of(context).showZdsToast(
ZdsToast(
rounded: false,
title: Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
ComponentStrings.of(context).get('COPIED_TO_CLIPBOARD', 'Copied to Clipboard'),
),
],
),
),
),
);
_selectionTimer = Timer(const Duration(seconds: 4), toggleSelection);
}
},
);
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('isSelected', isSelected));
}
}
31 changes: 31 additions & 0 deletions test/fixtures/test_app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:zds_flutter/zds_flutter.dart';

class TestApp extends StatelessWidget {
const TestApp({super.key, required this.builder});
final WidgetBuilder builder;
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
ComponentDelegate(testing: true),
],
home: ZetaProvider(
builder: (context, themeData, themeMode) {
return Scaffold(body: builder.call(context));
},
),
);
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(ObjectFlagProperty<WidgetBuilder>.has('builder', builder));
}
}
105 changes: 105 additions & 0 deletions test/lib/src/components/organisms/selectable_widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:zds_flutter/zds_flutter.dart';
import '../../../../fixtures/test_app.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();
var clipboardContent = 'Selectable Text';
setUp(() {
// Mock the clipboard data
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform,
(MethodCall methodCall) async {
if (methodCall.method == 'Clipboard.getData') {
return {'text': clipboardContent};
}
return null;
});
});
tearDown(() {
// Remove the mock handler
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null);
});
group('ZdsSelectableWidget Tests', () {
testWidgets('Displays child widget', (WidgetTester tester) async {
const childWidget = Text('Selectable Text');
clipboardContent = 'Selectable Text';
await tester.pumpWidget(
TestApp(
builder: (BuildContext context) {
return const ZdsSelectableWidget(
textToCopy: 'Selectable Text',
child: childWidget,
);
},
),
);
await tester.pump(const Duration(milliseconds: 100));
expect(find.byWidget(childWidget), findsOneWidget);
});
testWidgets('Copies plain text to clipboard on long press', (WidgetTester tester) async {
const childWidget = Text('Selectable Text');
clipboardContent = 'Selectable Text';
await tester.pumpWidget(
TestApp(
builder: (BuildContext context) {
return const ZdsSelectableWidget(
textToCopy: 'Selectable Text',
copyable: true,
child: childWidget,
);
},
),
);
await tester.pump(const Duration(milliseconds: 100));
await tester.longPress(find.byWidget(childWidget));
await tester.pumpAndSettle();
final clipboardData = await Clipboard.getData('text/plain');
expect(clipboardData?.text, clipboardContent);
});
testWidgets('Copies HTML text to clipboard as plain text on long press', (WidgetTester tester) async {
const childWidget = Text('Selectable HTML Text');
const htmlText = '<p>Selectable <b>HTML</b> Text</p>';
clipboardContent = 'Selectable HTML Text';
await tester.pumpWidget(
TestApp(
builder: (BuildContext context) {
return const ZdsSelectableWidget(
textToCopy: htmlText,
isHtmlData: true,
copyable: true,
child: childWidget,
);
},
),
);
await tester.pump(const Duration(milliseconds: 100));
await tester.longPress(find.byWidget(childWidget));
await tester.pumpAndSettle();
final clipboardData = await Clipboard.getData('text/plain');
expect(clipboardData?.text, 'Selectable HTML Text');
});
testWidgets('Does not copy text if copyable is false', (WidgetTester tester) async {
const childWidget = Text('Non-copyable Text');
clipboardContent = '';
await tester.pumpWidget(
TestApp(
builder: (BuildContext context) {
return const ZdsSelectableWidget(
textToCopy: 'Non-copyable Text',
copyable: false,
child: childWidget,
);
},
),
);
await tester.pump(const Duration(milliseconds: 100));
await tester.longPress(find.byWidget(childWidget));
await tester.pumpAndSettle();
final clipboardData = await Clipboard.getData('text/plain');
expect(clipboardData?.text, '');
});
});
}

0 comments on commit 7fc6bef

Please sign in to comment.