add device frame (wip)

This commit is contained in:
Vadim 2024-05-11 14:59:28 +02:00
parent 41b7730f82
commit 0c6519efa1
5 changed files with 163 additions and 13 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View file

@ -1,26 +1,37 @@
enum ScreenshotDevicePlatform { android, ios } enum ScreenshotDevicePlatform { android, ios }
final screenshotDevicesIos = [
ScreenshotDevice.fromDisplayName(
displayName: 'iPhone 8 Plus',
platform: ScreenshotDevicePlatform.ios,
),
ScreenshotDevice.fromDisplayName(
displayName: 'iPhone 13 Pro',
platform: ScreenshotDevicePlatform.ios,
screenshotFrameOffset: (dx: 72, dy: 60),
),
];
class ScreenshotDevice { class ScreenshotDevice {
final String name; final String name;
final ScreenshotDevicePlatform platform; final ScreenshotDevicePlatform platform;
final ({int dx, int dy}) screenshotFrameOffset;
const ScreenshotDevice({ const ScreenshotDevice({
required this.name, required this.name,
required this.platform, required this.platform,
this.screenshotFrameOffset = (dx: 0, dy: 0),
}); });
ScreenshotDevice.fromDisplayName({ ScreenshotDevice.fromDisplayName({
required String displayName, required String displayName,
required this.platform, required this.platform,
this.screenshotFrameOffset = (dx: 0, dy: 0),
}) : name = displayName.replaceAll(' ', '_').toLowerCase(); }) : name = displayName.replaceAll(' ', '_').toLowerCase();
String get systemOverlayPathLight => String get systemOverlayPathLight =>
'screenshots/assets/system_overlays/${platform.name}/${name}_system_overlay_light.png'; 'screenshots/assets/system_overlays/${platform.name}/${name}_system_overlay_light.png';
String get systemOverlayPathDark => String get systemOverlayPathDark =>
'screenshots/assets/system_overlays/${platform.name}/${name}_system_overlay_dark.png'; 'screenshots/assets/system_overlays/${platform.name}/${name}_system_overlay_dark.png';
String get deviceFramePath => 'screenshots/assets/frames/${platform.name}/${name}_frame.png';
} }
final screenshotDevicesIos = [
ScreenshotDevice.fromDisplayName(displayName: 'iPhone 8 Plus', platform: ScreenshotDevicePlatform.ios),
ScreenshotDevice.fromDisplayName(displayName: 'iPhone 13 Pro', platform: ScreenshotDevicePlatform.ios),
];

View file

@ -19,17 +19,20 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../integration_test/mocks/paid_features_mock.dart'; import '../integration_test/mocks/paid_features_mock.dart';
import '../integration_test/utils/widget_tester_actions.dart'; import '../integration_test/utils/widget_tester_actions.dart';
import 'screenshot_args.dart';
//https://stackoverflow.com/a/67186625/13167574 //https://stackoverflow.com/a/67186625/13167574
const _mockFilm = Film('Ilford HP5+', 400); const _mockFilm = Film('Ilford HP5+', 400);
final Color _lightThemeColor = primaryColorsList[5];
final Color _darkThemeColor = primaryColorsList[3];
final ThemeData _themeLight = themeFrom(_lightThemeColor, Brightness.light);
final ThemeData _themeDark = themeFrom(_darkThemeColor, Brightness.dark);
/// Just a screenshot generator. No expectations here. /// Just a screenshot generator. No expectations here.
void main() { void main() {
final binding = IntegrationTestWidgetsFlutterBinding(); final binding = IntegrationTestWidgetsFlutterBinding();
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final Color lightThemeColor = primaryColorsList[5];
final Color darkThemeColor = primaryColorsList[3];
void mockSharedPrefs(ThemeType theme, Color color) { void mockSharedPrefs(ThemeType theme, Color color) {
// ignore: invalid_use_of_visible_for_testing_member // ignore: invalid_use_of_visible_for_testing_member
@ -70,7 +73,7 @@ void main() {
/// Generates several screenshots with the light theme /// Generates several screenshots with the light theme
testWidgets('Generate light theme screenshots', (tester) async { testWidgets('Generate light theme screenshots', (tester) async {
mockSharedPrefs(ThemeType.light, lightThemeColor); mockSharedPrefs(ThemeType.light, _lightThemeColor);
await tester.pumpApplication( await tester.pumpApplication(
availableFilms: [_mockFilm], availableFilms: [_mockFilm],
filmsInUse: [_mockFilm], filmsInUse: [_mockFilm],
@ -114,7 +117,7 @@ void main() {
testWidgets( testWidgets(
'Generate dark theme screenshots', 'Generate dark theme screenshots',
(tester) async { (tester) async {
mockSharedPrefs(ThemeType.dark, darkThemeColor); mockSharedPrefs(ThemeType.dark, _darkThemeColor);
await tester.pumpApplication( await tester.pumpApplication(
availableFilms: [_mockFilm], availableFilms: [_mockFilm],
filmsInUse: [_mockFilm], filmsInUse: [_mockFilm],
@ -138,8 +141,21 @@ final String _platformFolder = Platform.isAndroid ? 'android' : 'ios';
extension on WidgetTester { extension on WidgetTester {
Future<void> takeScreenshot(IntegrationTestWidgetsFlutterBinding binding, String name) async { Future<void> takeScreenshot(IntegrationTestWidgetsFlutterBinding binding, String name) async {
final bool isDark = name.contains('dark-');
final Color backgroundColor = (isDark ? _themeDark : _themeLight).colorScheme.surface;
await binding.takeScreenshot( await binding.takeScreenshot(
"$_platformFolder/${const String.fromEnvironment('deviceName').replaceAll(' ', '_').toLowerCase()}/$name", ScreenshotArgs(
name: name,
deviceName: const String.fromEnvironment('deviceName').replaceAll(' ', '_').toLowerCase(),
platformFolder: _platformFolder,
backgroundColor: (
r: backgroundColor.red,
g: backgroundColor.green,
b: backgroundColor.blue,
a: backgroundColor.alpha,
),
isDark: isDark,
).toString(),
); );
await pumpAndSettle(); await pumpAndSettle();
} }

View file

@ -0,0 +1,55 @@
import 'dart:convert';
class ScreenshotArgs {
final String name;
final String deviceName;
final String platformFolder;
final ({int r, int g, int b, int a}) backgroundColor;
final bool isDark;
const ScreenshotArgs({
required this.name,
required this.deviceName,
required this.platformFolder,
required this.backgroundColor,
required this.isDark,
});
String toPath() => 'screenshots/generated/$platformFolder/$deviceName/$name.png';
@override
String toString() => jsonEncode(_toJson());
factory ScreenshotArgs.fromString(String data) => ScreenshotArgs._fromJson(jsonDecode(data) as Map<String, dynamic>);
factory ScreenshotArgs._fromJson(Map<String, dynamic> data) {
final colorChannels = data['backgroundColor'] as List;
return ScreenshotArgs(
name: data['name'] as String,
deviceName: data['deviceName'] as String,
platformFolder: data['platformFolder'] as String,
backgroundColor: (
r: colorChannels[0] as int,
g: colorChannels[1] as int,
b: colorChannels[2] as int,
a: colorChannels[3] as int,
),
isDark: data['isDark'] as bool,
);
}
Map<String, dynamic> _toJson() {
return {
"name": name,
"deviceName": deviceName,
"platformFolder": platformFolder,
"backgroundColor": [
backgroundColor.r,
backgroundColor.g,
backgroundColor.b,
backgroundColor.a,
],
"isDark": isDark,
};
}
}

View file

@ -1,15 +1,83 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:image/image.dart';
import 'package:integration_test/integration_test_driver_extended.dart'; import 'package:integration_test/integration_test_driver_extended.dart';
import '../screenshots/devices_config.dart';
import '../screenshots/screenshot_args.dart';
import 'utils/grant_camera_permission.dart'; import 'utils/grant_camera_permission.dart';
Future<void> main() async { Future<void> main() async {
await grantCameraPermission(); await grantCameraPermission();
await integrationDriver( await integrationDriver(
onScreenshot: (name, bytes, [args]) async { onScreenshot: (name, bytes, [_]) async {
final File image = await File('screenshots/generated/$name.png').create(recursive: true); final screenshotArgs = ScreenshotArgs.fromString(name);
image.writeAsBytesSync(bytes); final backgroundColor = ColorRgba8(
screenshotArgs.backgroundColor.r,
screenshotArgs.backgroundColor.g,
screenshotArgs.backgroundColor.b,
screenshotArgs.backgroundColor.a,
);
final platform =
screenshotArgs.platformFolder == 'ios' ? ScreenshotDevicePlatform.ios : ScreenshotDevicePlatform.ios;
final deviceName = screenshotArgs.deviceName;
final file = await File(screenshotArgs.toPath()).create(recursive: true);
// switch (platform) {
// case ScreenshotDevicePlatform.ios:
// final device = screenshotDevicesIos.firstWhere(
// (device) => device.name == deviceName,
// orElse: () => ScreenshotDevice(name: '', platform: platform),
// );
// Image screenshot = decodePng(Uint8List.fromList(bytes))!;
// screenshot = screenshot.addSystemOverlay(device, isDark: screenshotArgs.isDark);
// screenshot = screenshot.addDeviceFrame(device, backgroundColor);
// file.writeAsBytesSync(encodePng(screenshot));
// case ScreenshotDevicePlatform.android:
// file.writeAsBytesSync(bytes);
// }
file.writeAsBytesSync(bytes);
return true; return true;
}, },
); );
} }
extension ScreenshotImage on Image {
Image addSystemOverlay(ScreenshotDevice device, {required bool isDark}) {
final path = isDark ? device.systemOverlayPathDark : device.systemOverlayPathLight;
final statusBar = copyResize(
decodePng(File(path).readAsBytesSync())!,
width: width,
);
return compositeImage(this, statusBar);
}
Image addDeviceFrame(ScreenshotDevice device, Color backgroundColor) {
final screenshotRounded = copyCrop(
this,
x: 0,
y: 0,
width: width,
height: height,
);
final frame = decodePng(File(device.deviceFramePath).readAsBytesSync())!;
final expandedScreenshot = copyExpandCanvas(
copyExpandCanvas(
screenshotRounded,
newWidth: screenshotRounded.width + device.screenshotFrameOffset.dx,
newHeight: screenshotRounded.height + device.screenshotFrameOffset.dy,
position: ExpandCanvasPosition.bottomRight,
backgroundColor: backgroundColor,
),
newWidth: frame.width,
newHeight: frame.height,
position: ExpandCanvasPosition.topLeft,
backgroundColor: backgroundColor,
);
return compositeImage(expandedScreenshot, frame);
}
}