mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
screenshot work
This commit is contained in:
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -173,7 +173,7 @@ jobs:
|
||||
- name: Generate screenshots for App Stores
|
||||
if: inputs.build_github
|
||||
run: |
|
||||
flutter test test/screenshot_test.dart
|
||||
flutter test --update-goldens
|
||||
zip -r BikeControl.storeassets.zip screenshots
|
||||
echo "Screenshots generated successfully"
|
||||
|
||||
@@ -264,7 +264,9 @@ jobs:
|
||||
overwrite: true
|
||||
name: Releases
|
||||
path: |
|
||||
screenshots/device-GitHub-600x900.png
|
||||
screenshots/device-noFrame-1100x2390.png
|
||||
screenshots/trainer-noFrame-1100x2390.png
|
||||
screenshots/customization-noFrame-1100x2390.png
|
||||
build/BikeControl.screenshots.zip
|
||||
|
||||
#10 Extract Version
|
||||
@@ -280,7 +282,7 @@ jobs:
|
||||
if: inputs.build_github
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "build/app/outputs/flutter-apk/BikeControl.android.apk,build/macos/Build/Products/Release/BikeControl.macos.zip,build/BikeControl.screenshots.zip,screenshots/device-GitHub-600x900.png"
|
||||
artifacts: "build/app/outputs/flutter-apk/BikeControl.android.apk,build/macos/Build/Products/Release/BikeControl.macos.zip,build/BikeControl.screenshots.zip,screenshots/device-noFrame-1100x2390.png,screenshots/trainer-noFrame-1100x2390.png,screenshots/customization-noFrame-1100x2390.png"
|
||||
allowUpdates: true
|
||||
prerelease: true
|
||||
bodyFile: /tmp/release_body.md
|
||||
|
||||
@@ -78,6 +78,8 @@ The app connects to your Controller devices (such as Zwift ones) automatically.
|
||||
- Connect to the trainer app using Network
|
||||
- available on Android, iOS, macOS, Windows
|
||||
- supported by e.g. MyWhoosh, Rouvy and Zwift
|
||||
- Connect to supported trainer app using the [OpenBikeControl](https://openbikecontrol.org) protocol
|
||||
- available on Android, iOS, macOS, Windows
|
||||
|
||||
## Alternatives
|
||||
- [qdomyos-zwift](https://www.qzfitness.com/) directly controls the trainer (as opposed to controlling the trainer app). This can be useful if your trainer app does not support virtual shifting.
|
||||
|
||||
@@ -264,8 +264,8 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: screenshotMode ? 200 : null,
|
||||
height: screenshotMode ? 100 : null,
|
||||
width: screenshotMode ? 160 : null,
|
||||
height: screenshotMode ? 70 : null,
|
||||
child: Card(
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.background,
|
||||
@@ -286,8 +286,8 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
),
|
||||
if (batteryLevel != null)
|
||||
SizedBox(
|
||||
width: screenshotMode ? 200 : null,
|
||||
height: screenshotMode ? 100 : null,
|
||||
width: screenshotMode ? 160 : null,
|
||||
height: screenshotMode ? 70 : null,
|
||||
child: Card(
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.background,
|
||||
@@ -312,8 +312,8 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
),
|
||||
if (firmwareVersion != null)
|
||||
SizedBox(
|
||||
width: screenshotMode ? 200 : null,
|
||||
height: screenshotMode ? 100 : null,
|
||||
width: screenshotMode ? 160 : null,
|
||||
height: screenshotMode ? 70 : null,
|
||||
child: Card(
|
||||
filled: true,
|
||||
padding: EdgeInsets.all(12),
|
||||
@@ -342,8 +342,8 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
),
|
||||
if (rssi != null)
|
||||
SizedBox(
|
||||
width: screenshotMode ? 200 : null,
|
||||
height: screenshotMode ? 100 : null,
|
||||
width: screenshotMode ? 160 : null,
|
||||
height: screenshotMode ? 70 : null,
|
||||
child: Card(
|
||||
filled: true,
|
||||
padding: EdgeInsets.all(12),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/pages/button_edit.dart';
|
||||
import 'package:swift_control/utils/core.dart';
|
||||
import 'package:swift_control/utils/i18n_extension.dart';
|
||||
@@ -57,7 +58,7 @@ class _ConfigurationPageState extends State<ConfigurationPage> {
|
||||
children: [
|
||||
Select<SupportedApp>(
|
||||
constraints: BoxConstraints(maxWidth: 400, minWidth: 400),
|
||||
itemBuilder: (c, app) => Text(app.name),
|
||||
itemBuilder: (c, app) => Text(screenshotMode ? 'Trainer app' : app.name),
|
||||
popup: SelectPopup(
|
||||
items: SelectItemList(
|
||||
children: SupportedApp.supportedApps.map((app) {
|
||||
@@ -94,7 +95,9 @@ class _ConfigurationPageState extends State<ConfigurationPage> {
|
||||
if (core.settings.getTrainerApp() != null) ...[
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
context.i18n.selectTargetWhereAppRuns(core.settings.getTrainerApp()?.name ?? 'the Trainer app'),
|
||||
context.i18n.selectTargetWhereAppRuns(
|
||||
screenshotMode ? 'Trainer app' : core.settings.getTrainerApp()?.name ?? 'the Trainer app',
|
||||
),
|
||||
).small,
|
||||
Flex(
|
||||
direction: isMobile ? Axis.vertical : Axis.horizontal,
|
||||
|
||||
@@ -127,7 +127,9 @@ class _NavigationState extends State<Navigation> {
|
||||
return Scaffold(
|
||||
headers: [
|
||||
AppBar(
|
||||
padding: const EdgeInsets.only(top: 12, bottom: 8, left: 12, right: 12) * Theme.of(context).scaling,
|
||||
padding:
|
||||
const EdgeInsets.only(top: 12, bottom: 8, left: 12, right: 12) *
|
||||
(screenshotMode ? 2 : Theme.of(context).scaling),
|
||||
title: AppTitle(),
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
trailing: buildMenuButtons(
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/core.dart';
|
||||
import 'package:swift_control/utils/i18n_extension.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
@@ -455,7 +456,7 @@ class _KeyWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
label.splitByUpperCase(),
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: screenshotMode ? null : 'monospace',
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.primaryForeground,
|
||||
),
|
||||
|
||||
@@ -176,6 +176,7 @@ class CoreLogic {
|
||||
AppInfo? get obpConnectedApp => core.obpMdnsEmulator.isConnected.value ?? core.obpBluetoothEmulator.isConnected.value;
|
||||
|
||||
bool get emulatorEnabled =>
|
||||
screenshotMode ||
|
||||
(core.settings.getMyWhooshLinkEnabled() && showMyWhooshLink) ||
|
||||
(core.settings.getZwiftBleEmulatorEnabled() && showZwiftBleEmulator) ||
|
||||
(core.settings.getZwiftMdnsEmulatorEnabled() && showZwiftMsdnEmulator) ||
|
||||
@@ -195,6 +196,7 @@ class CoreLogic {
|
||||
((showLocalControl && core.settings.getLocalEnabled()) || (isRemoteControlEnabled));
|
||||
|
||||
bool get hasNoConnectionMethod =>
|
||||
!screenshotMode &&
|
||||
!isZwiftBleEnabled &&
|
||||
!isZwiftMdnsEnabled &&
|
||||
!showObpActions &&
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/keymap/apps/supported_app.dart';
|
||||
import 'package:swift_control/utils/requirements/multi.dart';
|
||||
|
||||
@@ -13,7 +14,7 @@ class MyWhoosh extends SupportedApp {
|
||||
packageName: "com.mywhoosh.whooshgame",
|
||||
compatibleTargets: Target.values,
|
||||
supportsZwiftEmulation: false,
|
||||
supportsOpenBikeProtocol: false,
|
||||
supportsOpenBikeProtocol: screenshotMode,
|
||||
keymap: Keymap(
|
||||
keyPairs: [
|
||||
...ControllerButton.values
|
||||
|
||||
@@ -16,9 +16,7 @@ class TrainingPeaks extends SupportedApp {
|
||||
: super(
|
||||
name: 'TrainingPeaks Virtual / IndieVelo',
|
||||
packageName: "com.indieVelo.client",
|
||||
compatibleTargets: !kIsWeb && Platform.isIOS
|
||||
? Target.values.filterNot((e) => e == Target.thisDevice).toList()
|
||||
: Target.values,
|
||||
compatibleTargets: !kIsWeb && Platform.isIOS ? [Target.otherDevice] : Target.values,
|
||||
supportsZwiftEmulation: false,
|
||||
supportsOpenBikeProtocol: false,
|
||||
keymap: Keymap(
|
||||
|
||||
@@ -163,10 +163,10 @@ class _ButtonEditor extends StatelessWidget {
|
||||
currentProfile,
|
||||
skipName: '$currentProfile (Copy)',
|
||||
);
|
||||
if (newName != null) {
|
||||
if (newName != null && context.mounted) {
|
||||
buildToast(context, title: context.i18n.createdNewCustomProfile(newName));
|
||||
final selectedKeyPair = core.actionHandler.supportedApp!.keymap.keyPairs.firstWhere(
|
||||
(e) => e == this.keyPair,
|
||||
(e) => e == keyPair,
|
||||
);
|
||||
await openDrawer(
|
||||
context: context,
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:golden_screenshot/golden_screenshot.dart';
|
||||
import 'package:swift_control/widgets/ui/colors.dart';
|
||||
|
||||
import 'screenshot_test.dart';
|
||||
|
||||
class CustomFrame extends StatelessWidget {
|
||||
const CustomFrame({
|
||||
super.key,
|
||||
@@ -12,7 +14,7 @@ class CustomFrame extends StatelessWidget {
|
||||
required this.platform,
|
||||
});
|
||||
|
||||
final TargetPlatform platform;
|
||||
final DeviceType platform;
|
||||
final String title;
|
||||
final ScreenshotDevice device;
|
||||
final ScreenshotFrameColors? frameColors;
|
||||
@@ -20,61 +22,64 @@ class CustomFrame extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [BKColor.main, BKColor.mainEnd],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 54, horizontal: 8),
|
||||
child: Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
final borderRadiusValue = 26.0;
|
||||
return platform == DeviceType.noFrame
|
||||
? Scaffold(body: child)
|
||||
: Scaffold(
|
||||
body: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [BKColor.main, BKColor.mainEnd],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 170,
|
||||
left: 8,
|
||||
right: 8,
|
||||
bottom: -30,
|
||||
child: FittedBox(
|
||||
child: Container(
|
||||
width: device.resolution.width / device.pixelRatio,
|
||||
height: device.resolution.height / device.pixelRatio,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 54, horizontal: 8),
|
||||
child: Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
foregroundDecoration: BoxDecoration(
|
||||
border: Border.all(width: 8),
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
Positioned(
|
||||
top: 170,
|
||||
left: 8,
|
||||
right: 8,
|
||||
bottom: -30,
|
||||
child: FittedBox(
|
||||
child: Container(
|
||||
width: device.resolution.width / device.pixelRatio,
|
||||
height: device.resolution.height / device.pixelRatio,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(borderRadiusValue),
|
||||
),
|
||||
foregroundDecoration: BoxDecoration(
|
||||
border: Border.all(width: 8),
|
||||
borderRadius: BorderRadius.circular(borderRadiusValue),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: switch (platform) {
|
||||
DeviceType.android => ScreenshotFrame.androidPhone(device: device, child: child),
|
||||
DeviceType.androidTablet => ScreenshotFrame.androidTablet(device: device, child: child),
|
||||
DeviceType.iPhone => ScreenshotFrame.iphone(device: device, child: child),
|
||||
DeviceType.iPad => ScreenshotFrame.ipad(device: device, child: child),
|
||||
DeviceType.desktop => ScreenshotFrame.noFrame(device: device, child: child),
|
||||
DeviceType.noFrame => throw UnimplementedError(),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: switch (platform) {
|
||||
TargetPlatform.android => ScreenshotFrame.androidPhone(device: device, child: child),
|
||||
TargetPlatform.fuchsia => throw UnimplementedError(),
|
||||
TargetPlatform.iOS => ScreenshotFrame.iphone(device: device, child: child),
|
||||
TargetPlatform.linux => throw UnimplementedError(),
|
||||
TargetPlatform.macOS => ScreenshotFrame.noFrame(device: device, child: child),
|
||||
TargetPlatform.windows => ScreenshotFrame.noFrame(device: device, child: child),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,15 @@ import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
import 'custom_frame.dart';
|
||||
|
||||
enum DeviceType {
|
||||
android,
|
||||
androidTablet,
|
||||
iPhone,
|
||||
iPad,
|
||||
desktop,
|
||||
noFrame,
|
||||
}
|
||||
|
||||
Future<void> main() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
PackageInfo.setMockInitialValues(
|
||||
@@ -45,9 +54,13 @@ Future<void> main() async {
|
||||
..batteryLevel = 81,
|
||||
]);
|
||||
|
||||
final List<(TargetPlatform type, Size size)> sizes = [
|
||||
(TargetPlatform.android, Size(1320, 2868)),
|
||||
(TargetPlatform.iOS, Size(1320, 2868)),
|
||||
final List<({DeviceType type, TargetPlatform platform, Size size})> sizes = [
|
||||
(type: DeviceType.android, platform: TargetPlatform.android, size: Size(1320, 2868)),
|
||||
(type: DeviceType.androidTablet, platform: TargetPlatform.android, size: Size(3840, 2400)),
|
||||
(type: DeviceType.iPhone, platform: TargetPlatform.iOS, size: Size(1242, 2688)),
|
||||
(type: DeviceType.iPad, platform: TargetPlatform.iOS, size: Size(2752, 2064)),
|
||||
(type: DeviceType.desktop, platform: TargetPlatform.windows, size: Size(2752, 2064)),
|
||||
(type: DeviceType.noFrame, platform: TargetPlatform.windows, size: Size(1320, 2868) / 1.2),
|
||||
/*('iPhone', Size(1242, 2688)),
|
||||
('macOS', Size(1280, 800)),
|
||||
('GitHub', Size(600, 900)),*/
|
||||
@@ -56,15 +69,15 @@ Future<void> main() async {
|
||||
debugDisableShadows = true;
|
||||
screenshotMode = true;
|
||||
|
||||
testGoldens('Device', (WidgetTester tester) async {
|
||||
testGoldens('Init', (WidgetTester tester) async {
|
||||
screenshotMode = true;
|
||||
await tester.loadAssets();
|
||||
|
||||
for (final size in sizes) {
|
||||
await tester.pumpWidget(
|
||||
ScreenshotApp(
|
||||
device: ScreenshotDevice(
|
||||
platform: size.$1,
|
||||
resolution: size.$2,
|
||||
platform: size.platform,
|
||||
resolution: size.size,
|
||||
pixelRatio: 3,
|
||||
goldenSubFolder: 'iphoneScreenshots/',
|
||||
frameBuilder:
|
||||
@@ -73,7 +86,37 @@ Future<void> main() async {
|
||||
required ScreenshotFrameColors? frameColors,
|
||||
required Widget child,
|
||||
}) => CustomFrame(
|
||||
platform: size.$1,
|
||||
platform: size.type,
|
||||
title: 'BikeControl connects to your favorite controller',
|
||||
device: device,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
home: BikeControlApp(
|
||||
page: BCPage.devices,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
}
|
||||
});
|
||||
testGoldens('Device', (WidgetTester tester) async {
|
||||
for (final size in sizes) {
|
||||
await tester.pumpWidget(
|
||||
ScreenshotApp(
|
||||
device: ScreenshotDevice(
|
||||
platform: size.platform,
|
||||
resolution: size.size,
|
||||
pixelRatio: 3,
|
||||
goldenSubFolder: 'iphoneScreenshots/',
|
||||
frameBuilder:
|
||||
({
|
||||
required ScreenshotDevice device,
|
||||
required ScreenshotFrameColors? frameColors,
|
||||
required Widget child,
|
||||
}) => CustomFrame(
|
||||
platform: size.type,
|
||||
title: 'BikeControl connects to your favorite controller',
|
||||
device: device,
|
||||
child: child,
|
||||
@@ -89,19 +132,20 @@ Future<void> main() async {
|
||||
await expectLater(
|
||||
find.byType(ma.Scaffold),
|
||||
matchesGoldenFile(
|
||||
'../screenshots/device-${size.$1.name}-${size.$2.width.toInt()}-${size.$2.height.toInt()}.png',
|
||||
'../screenshots/device-${size.type.name}-${size.size.width.toInt()}x${size.size.height.toInt()}.png',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testGoldens('Trainer', (WidgetTester tester) async {
|
||||
screenshotMode = true;
|
||||
for (final size in sizes) {
|
||||
await tester.pumpWidget(
|
||||
ScreenshotApp(
|
||||
device: ScreenshotDevice(
|
||||
platform: size.$1,
|
||||
resolution: size.$2,
|
||||
platform: size.platform,
|
||||
resolution: size.size,
|
||||
pixelRatio: 3,
|
||||
goldenSubFolder: 'iphoneScreenshots/',
|
||||
frameBuilder:
|
||||
@@ -110,8 +154,8 @@ Future<void> main() async {
|
||||
required ScreenshotFrameColors? frameColors,
|
||||
required Widget child,
|
||||
}) => CustomFrame(
|
||||
platform: size.$1,
|
||||
title: 'BikeControl connects to your favorite controller',
|
||||
platform: size.type,
|
||||
title: 'Choose how BikeControl connects to your trainer',
|
||||
device: device,
|
||||
child: child,
|
||||
),
|
||||
@@ -126,7 +170,7 @@ Future<void> main() async {
|
||||
await expectLater(
|
||||
find.byType(ma.Scaffold),
|
||||
matchesGoldenFile(
|
||||
'../screenshots/trainer-${size.$1.name}-${size.$2.width.toInt()}-${size.$2.height.toInt()}.png',
|
||||
'../screenshots/trainer-${size.type.name}-${size.size.width.toInt()}x${size.size.height.toInt()}.png',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -139,8 +183,8 @@ Future<void> main() async {
|
||||
await tester.pumpWidget(
|
||||
ScreenshotApp(
|
||||
device: ScreenshotDevice(
|
||||
platform: size.$1,
|
||||
resolution: size.$2,
|
||||
platform: size.platform,
|
||||
resolution: size.size,
|
||||
pixelRatio: 3,
|
||||
goldenSubFolder: 'iphoneScreenshots/',
|
||||
frameBuilder:
|
||||
@@ -149,7 +193,7 @@ Future<void> main() async {
|
||||
required ScreenshotFrameColors? frameColors,
|
||||
required Widget child,
|
||||
}) => CustomFrame(
|
||||
platform: size.$1,
|
||||
platform: size.type,
|
||||
title: 'Customize every controller button',
|
||||
device: device,
|
||||
child: child,
|
||||
@@ -165,7 +209,7 @@ Future<void> main() async {
|
||||
await expectLater(
|
||||
find.byType(ma.Scaffold),
|
||||
matchesGoldenFile(
|
||||
'../screenshots/customization-${size.$1.name}-${size.$2.width.toInt()}-${size.$2.height.toInt()}.png',
|
||||
'../screenshots/customization-${size.type.name}-${size.size.width.toInt()}x${size.size.height.toInt()}.png',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user