Skip to content

Commit

Permalink
Merge pull request #1 from tandt53/feat/get-text-command-extension
Browse files Browse the repository at this point in the history
feat: add new command to get text
  • Loading branch information
Alpaca00 authored Dec 1, 2024
2 parents 6fe10ac + 31c31f2 commit 4d199bd
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 21 deletions.
File renamed without changes.
180 changes: 180 additions & 0 deletions lib/get_text_command.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_driver/src/common/find.dart';
import 'package:flutter_driver/src/common/message.dart';
import 'package:flutter_test/flutter_test.dart';

class Base64URL {
static String encode(String str) {
String base64 = base64Encode(utf8.encode(str));
return base64.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
}

static String decode(String str) {
String base64 = str.replaceAll('-', '+').replaceAll('_', '/');

// Add padding if needed
switch (base64.length % 4) {
case 2:
base64 += '==';
break;
case 3:
base64 += '=';
break;
}

return utf8.decode(base64Decode(base64));
}
}

class FinderHelper {
static SerializableFinder deserializeBase64(String base64Str) {
try {
// Decode base64 to JSON string
final jsonStr = Base64URL.decode(base64Str);

// Parse JSON
final dynamic finderData = json.decode(jsonStr);

if (finderData is! Map<String, dynamic>) {
throw Exception('finder is not valid');
}

if (!finderData.containsKey('finderType')) {
throw Exception('Invalid finder format: missing finderType');
}

final String finderType = finderData['finderType'] as String;

switch (finderType) {
case 'ByText':
return ByText(finderData['text'] as String);

case 'ByType':
return ByType(finderData['type'] as String);

case 'ByValueKey':
final keyType = finderData['keyValueType'] as String?;
final keyValue = finderData['keyValueString'] as String;

if (keyType == 'int') {
return ByValueKey(int.parse(keyValue));
}
return ByValueKey(keyValue);

case 'Ancestor':
// Parse of and matching which are JSON strings
final ofJson = json.decode(finderData['of'] as String);
final matchingJson = json.decode(finderData['matching'] as String);

return Ancestor(
of: deserializeBase64(Base64URL.encode(json.encode(ofJson))),
matching:
deserializeBase64(Base64URL.encode(json.encode(matchingJson))),
matchRoot: finderData['matchRoot'] == 'true',
firstMatchOnly: finderData['firstMatchOnly'] == 'true',
);

case 'Descendant':
final ofJson = json.decode(finderData['of'] as String);
final matchingJson = json.decode(finderData['matching'] as String);

return Descendant(
of: deserializeBase64(Base64URL.encode(json.encode(ofJson))),
matching:
deserializeBase64(Base64URL.encode(json.encode(matchingJson))),
matchRoot: finderData['matchRoot'] == 'true',
firstMatchOnly: finderData['firstMatchOnly'] == 'true',
);

default:
throw Exception('Unsupported finder type: $finderType');
}
} catch (e) {
throw Exception('Error deserializing finder: $e');
}
}
}

class GetTextCommandExtension extends CommandExtension {
String? getTextFromWidget(Text widget) {
return widget.data ?? widget.textSpan?.toPlainText();
}

@override
Future<Result> call(
Command command,
WidgetController prober,
CreateFinderFactory finderFactory,
CommandHandlerFactory handlerFactory) async {
final GetTextCommand dragCommand = command as GetTextCommand;

// Create finder for Text widget
final type = dragCommand.base64Element;
// decodeBase64 to json
SerializableFinder serializableFinder =
FinderHelper.deserializeBase64(type);

final Finder finder = finderFactory.createFinder(serializableFinder);

// Get the widget element
final Element element = prober.element(finder);

// if element is not a Text widget, return false with error
if (element.widget is! Text) {
return const GetTextResult(false, data: {
'errorCode': 'NOT_A_TEXT_WIDGET',
'error': 'Found element is not a Text widget'
});
}

final text = getTextFromWidget(element.widget as Text);
return text != null
? GetTextResult(true, data: {'text': text})
: const GetTextResult(false, data: {
'errorCode': 'NO_TEXT_CONTENT',
'error': 'No text content found'
});
}

@override
String get commandKind => 'getTextWithCommandExtension';

@override
Command deserialize(
Map<String, String> params,
DeserializeFinderFactory finderFactory,
DeserializeCommandFactory commandFactory) {
return GetTextCommand.deserialize(params);
}
}

class GetTextCommand extends Command {
final String base64Element;

GetTextCommand(this.base64Element);

@override
String get kind => 'getTextWithCommandExtension';

GetTextCommand.deserialize(Map<String, String> params)
: base64Element = params['findBy']!;
}

class GetTextResult extends Result {
final bool success;
final Map<String, dynamic>? data;

const GetTextResult(this.success, {this.data});

@override
Map<String, dynamic> toJson() {
return <String, dynamic>{
'success': success,
if (data != null) ...data!,
};
}
}
80 changes: 60 additions & 20 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_driver/src/extension/extension.dart';

import 'extended_commands.dart';

import 'drag_commands.dart';
import 'get_text_command.dart';

void main() {
enableFlutterDriverExtension(
commands: [DragCommandExtension()]);
enableFlutterDriverExtension(commands: [DragCommandExtension(), GetTextCommandExtension()]);
runApp(const MyApp());
}

Expand All @@ -29,6 +28,7 @@ class MyApp extends StatelessWidget {

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});

final String title;

@override
Expand All @@ -45,22 +45,62 @@ class _MyHomePageState extends State<MyHomePage> {
title: Text(widget.title),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: ReorderableListView(
padding: const EdgeInsets.all(8),
children: items.map((item) {
return ListTile(
key: ValueKey(item),
title: Text(item),
tileColor: Colors.grey.shade200,
);
}).toList(),
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) newIndex -= 1;
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
body: Column(
children: [
// Original ReorderableListView
Expanded(
child: ReorderableListView(
padding: const EdgeInsets.all(8),
children: items.map((item) {
return ListTile(
key: ValueKey(item),
title: Text(item),
tileColor: Colors.grey.shade200,
);
}).toList(),
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) newIndex -= 1;
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
),
),
// TextSpan example
const Padding(
padding: EdgeInsets.all(16.0),
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: 'Amount: ',
style: TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
TextSpan(
text: '100',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
TextSpan(
text: ' USD',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
key: ValueKey('amount'),
),
),
],
),
);
}
Expand Down
2 changes: 1 addition & 1 deletion test_driver/app.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:demo/extended_commands.dart';
import 'package:demo/drag_commands.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:demo/main.dart' as app;

Expand Down

0 comments on commit 4d199bd

Please sign in to comment.