mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
restructure UI to make target selection easier to understand as well as how to get help
This commit is contained in:
4
.github/workflows/patch.yml
vendored
4
.github/workflows/patch.yml
vendored
@@ -75,7 +75,6 @@ jobs:
|
||||
echo "${{ secrets.KEYSTORE_PROPERTIES }}" > android/keystore.properties;
|
||||
|
||||
- name: 🚀 Shorebird Patch macOS
|
||||
if: false
|
||||
uses: shorebirdtech/shorebird-patch@v1
|
||||
with:
|
||||
platform: macos
|
||||
@@ -87,7 +86,7 @@ jobs:
|
||||
with:
|
||||
platform: android
|
||||
release-version: latest
|
||||
args: '--allow-asset-diffs --allow-native-diffs'
|
||||
args: '--allow-asset-diffs'
|
||||
|
||||
- name: 🚀 Shorebird Patch iOS
|
||||
uses: shorebirdtech/shorebird-patch@v1
|
||||
@@ -136,7 +135,6 @@ jobs:
|
||||
token: ${{ secrets.TOKEN }}
|
||||
|
||||
windows:
|
||||
if: false
|
||||
name: Patch Windows
|
||||
runs-on: windows-latest
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import 'package:swift_control/utils/actions/android.dart';
|
||||
import 'package:swift_control/utils/actions/desktop.dart';
|
||||
import 'package:swift_control/utils/actions/remote.dart';
|
||||
import 'package:swift_control/utils/settings/settings.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'bluetooth/connection.dart';
|
||||
import 'utils/actions/base_actions.dart';
|
||||
@@ -25,13 +24,6 @@ const screenshotMode = false;
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
initializeActions(true);
|
||||
if (actionHandler is DesktopActions) {
|
||||
// Must add this line.
|
||||
await windowManager.ensureInitialized();
|
||||
windowManager.setSize(Size(1280, 800));
|
||||
}
|
||||
|
||||
runApp(const SwiftPlayApp());
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:swift_control/bluetooth/devices/zwift/zwift_device.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/pages/markdown.dart';
|
||||
import 'package:swift_control/pages/touch_area.dart';
|
||||
import 'package:swift_control/utils/actions/base_actions.dart';
|
||||
import 'package:swift_control/utils/actions/desktop.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
import 'package:swift_control/widgets/loading_widget.dart';
|
||||
@@ -159,8 +160,7 @@ class _DevicePageState extends State<DevicePage> with WidgetsBindingObserver {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (connection.devices.isEmpty)
|
||||
Text('No devices connected. Go back and connect a device to get started.'),
|
||||
if (connection.devices.isEmpty) Text('No devices connected. Searching...'),
|
||||
...connection.devices.map(
|
||||
(device) => Row(
|
||||
children: [
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/requirements/multi.dart';
|
||||
import 'package:swift_control/utils/requirements/platform.dart';
|
||||
import 'package:swift_control/widgets/changelog_dialog.dart';
|
||||
import 'package:swift_control/widgets/menu.dart';
|
||||
@@ -21,7 +22,6 @@ class RequirementsPage extends StatefulWidget {
|
||||
|
||||
class _RequirementsPageState extends State<RequirementsPage> with WidgetsBindingObserver {
|
||||
int _currentStep = 0;
|
||||
var _local = true;
|
||||
|
||||
List<PlatformRequirement> _requirements = [];
|
||||
|
||||
@@ -30,8 +30,6 @@ class _RequirementsPageState extends State<RequirementsPage> with WidgetsBinding
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
_local = kIsWeb || !Platform.isIOS;
|
||||
|
||||
// call after first frame
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
settings.init().then((_) {
|
||||
@@ -94,90 +92,61 @@ class _RequirementsPageState extends State<RequirementsPage> with WidgetsBinding
|
||||
),
|
||||
body: _requirements.isEmpty
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (!kIsWeb)
|
||||
SwitchListTile.adaptive(
|
||||
value: _local,
|
||||
title: Text('Trainer app is running on this device'),
|
||||
subtitle: Text('Turn off if you want to control another device, e.g. your tablet'),
|
||||
onChanged: (local) {
|
||||
if (Platform.isIOS) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('This platform only supports controlling trainer apps on other devices'),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
initializeActions(local);
|
||||
: Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
child: Stepper(
|
||||
currentStep: _currentStep,
|
||||
connectorColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) => Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onStepContinue: _currentStep < _requirements.length
|
||||
? () {
|
||||
setState(() {
|
||||
_local = local;
|
||||
_reloadRequirements();
|
||||
_currentStep += 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Stepper(
|
||||
currentStep: _currentStep,
|
||||
connectorColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) => Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onStepContinue: _currentStep < _requirements.length
|
||||
? () {
|
||||
setState(() {
|
||||
_currentStep += 1;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
onStepTapped: (step) {
|
||||
if (_requirements[step].status) {
|
||||
return;
|
||||
}
|
||||
final hasEarlierIncomplete = _requirements.indexWhere((req) => !req.status) < step;
|
||||
if (hasEarlierIncomplete && !kDebugMode) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_currentStep = step;
|
||||
});
|
||||
},
|
||||
controlsBuilder: (context, details) => Container(),
|
||||
steps: _requirements
|
||||
.mapIndexed(
|
||||
(index, req) => Step(
|
||||
title: Text(req.name, style: TextStyle(fontWeight: FontWeight.w600)),
|
||||
subtitle: req.description != null ? Text(req.description!) : null,
|
||||
content: Container(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child:
|
||||
(index == _currentStep
|
||||
? req.build(context, () {
|
||||
_reloadRequirements();
|
||||
})
|
||||
: null) ??
|
||||
ElevatedButton(
|
||||
onPressed: req.status
|
||||
? null
|
||||
: () => _callRequirement(req, context, () {
|
||||
_reloadRequirements();
|
||||
}),
|
||||
child: Text(req.name),
|
||||
),
|
||||
: null,
|
||||
onStepTapped: (step) {
|
||||
if (_requirements[step].status && _requirements[step] is! TargetRequirement) {
|
||||
return;
|
||||
}
|
||||
final hasEarlierIncomplete = _requirements.indexWhere((req) => !req.status) < step;
|
||||
if (hasEarlierIncomplete) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_currentStep = step;
|
||||
});
|
||||
},
|
||||
controlsBuilder: (context, details) => Container(),
|
||||
steps: _requirements
|
||||
.mapIndexed(
|
||||
(index, req) => Step(
|
||||
title: Text(req.name, style: TextStyle(fontWeight: FontWeight.w600)),
|
||||
subtitle: req.buildDescription() ?? (req.description != null ? Text(req.description!) : null),
|
||||
content: Container(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child:
|
||||
(index == _currentStep
|
||||
? req.build(context, () {
|
||||
_reloadRequirements();
|
||||
})
|
||||
: null) ??
|
||||
ElevatedButton(
|
||||
onPressed: req.status
|
||||
? null
|
||||
: () => _callRequirement(req, context, () {
|
||||
_reloadRequirements();
|
||||
}),
|
||||
child: Text(req.name),
|
||||
),
|
||||
state: req.status ? StepState.complete : StepState.indexed,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
state: req.status ? StepState.complete : StepState.indexed,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -189,7 +158,7 @@ class _RequirementsPageState extends State<RequirementsPage> with WidgetsBinding
|
||||
}
|
||||
|
||||
void _reloadRequirements() {
|
||||
getRequirements(_local).then((req) {
|
||||
getRequirements(settings.getLastTarget() == Target.thisDevice).then((req) {
|
||||
_requirements = req;
|
||||
_currentStep = req.indexWhere((req) => !req.status);
|
||||
if (mounted) {
|
||||
|
||||
@@ -79,3 +79,144 @@ class BluetoothScanning extends PlatformRequirement {
|
||||
return ScanWidget();
|
||||
}
|
||||
}
|
||||
|
||||
typedef BoolFunction = bool Function();
|
||||
|
||||
enum Target {
|
||||
thisDevice(title: 'This device', description: 'Trainer app runs on this device', icon: Icons.devices),
|
||||
iPad(
|
||||
title: 'iPad',
|
||||
description: 'Remotely control the trainer app on an iPad',
|
||||
icon: Icons.settings_remote_outlined,
|
||||
),
|
||||
android(
|
||||
title: 'Android Device',
|
||||
description: 'Remotely control the trainer app on an Android device',
|
||||
icon: Icons.settings_remote_outlined,
|
||||
),
|
||||
macOS(
|
||||
title: 'Mac',
|
||||
description: 'Remotely control the trainer app on a Mac',
|
||||
icon: Icons.settings_remote_outlined,
|
||||
),
|
||||
windows(
|
||||
title: 'Windows PC',
|
||||
description: 'Remotely control the trainer app on a Windows PC',
|
||||
icon: Icons.settings_remote_outlined,
|
||||
);
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final IconData icon;
|
||||
|
||||
const Target({required this.title, required this.description, required this.icon});
|
||||
|
||||
bool get isCompatible {
|
||||
return switch (this) {
|
||||
Target.thisDevice => !Platform.isIOS,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
String? get warning {
|
||||
return switch (this) {
|
||||
Target.android when Platform.isAndroid =>
|
||||
"Download and use SwiftControl on that Android device or select 'This device'.",
|
||||
Target.macOS when Platform.isMacOS =>
|
||||
"Download and use SwiftControl on that macOS device or select 'This device'.",
|
||||
Target.windows when Platform.isWindows =>
|
||||
"Download and use SwiftControl on that Windows device or select 'This device'.",
|
||||
Target.android => "Download and use SwiftControl on that Android device.",
|
||||
Target.macOS => "Download and use SwiftControl on that macOS device.",
|
||||
Target.windows => "Download and use SwiftControl on that Windows device.",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TargetRequirement extends PlatformRequirement {
|
||||
TargetRequirement()
|
||||
: super(
|
||||
'Select Target Device',
|
||||
description: 'Select your Target Device where you want to run your trainer app on',
|
||||
) {
|
||||
status = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> call(BuildContext context, VoidCallback onUpdate) async {}
|
||||
|
||||
@override
|
||||
Future<void> getStatus() async {
|
||||
status = settings.getLastTarget() != null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context, VoidCallback onUpdate) {
|
||||
return DropdownMenu<Target>(
|
||||
dropdownMenuEntries: Target.values.map((target) {
|
||||
return DropdownMenuEntry(
|
||||
value: target,
|
||||
label: target.title,
|
||||
enabled: target.isCompatible,
|
||||
trailingIcon: Icon(target.icon),
|
||||
labelWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(target.title, style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
target.isCompatible
|
||||
? target.description
|
||||
: 'Due to iOS restrictions only controlling trainer apps on other devices is supported :(',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
hintText: name,
|
||||
initialSelection: settings.getLastTarget(),
|
||||
onSelected: (target) async {
|
||||
if (target != null) {
|
||||
await settings.setLastTarget(target);
|
||||
initializeActions(target == Target.thisDevice);
|
||||
if (target.warning != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(target.warning!),
|
||||
duration: Duration(seconds: 10),
|
||||
),
|
||||
);
|
||||
}
|
||||
onUpdate();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? buildDescription() {
|
||||
final target = settings.getLastTarget();
|
||||
|
||||
if (target != null) {
|
||||
if (target.warning != null) {
|
||||
return Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(Icons.warning, color: Colors.red, size: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
settings.getLastTarget()!.warning!,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Text(target.title);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ abstract class PlatformRequirement {
|
||||
Widget? build(BuildContext context, VoidCallback onUpdate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget? buildDescription() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PlatformRequirement>> getRequirements(bool local) async {
|
||||
@@ -34,15 +38,26 @@ Future<List<PlatformRequirement>> getRequirements(bool local) async {
|
||||
list = [BluetoothTurnedOn(), BluetoothScanning()];
|
||||
}
|
||||
} else if (Platform.isMacOS) {
|
||||
list = [BluetoothTurnedOn(), local ? KeyboardRequirement() : RemoteRequirement(), BluetoothScanning()];
|
||||
list = [
|
||||
TargetRequirement(),
|
||||
BluetoothTurnedOn(),
|
||||
local ? KeyboardRequirement() : RemoteRequirement(),
|
||||
BluetoothScanning(),
|
||||
];
|
||||
} else if (Platform.isIOS) {
|
||||
list = [BluetoothTurnedOn(), RemoteRequirement(), BluetoothScanning()];
|
||||
list = [TargetRequirement(), BluetoothTurnedOn(), RemoteRequirement(), BluetoothScanning()];
|
||||
} else if (Platform.isWindows) {
|
||||
list = [BluetoothTurnedOn(), local ? KeyboardRequirement() : RemoteRequirement(), BluetoothScanning()];
|
||||
list = [
|
||||
TargetRequirement(),
|
||||
BluetoothTurnedOn(),
|
||||
local ? KeyboardRequirement() : RemoteRequirement(),
|
||||
BluetoothScanning(),
|
||||
];
|
||||
} else if (Platform.isAndroid) {
|
||||
final deviceInfoPlugin = DeviceInfoPlugin();
|
||||
final deviceInfo = await deviceInfoPlugin.androidInfo;
|
||||
list = [
|
||||
TargetRequirement(),
|
||||
BluetoothTurnedOn(),
|
||||
local ? AccessibilityRequirement() : RemoteRequirement(),
|
||||
NotificationRequirement(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart' hide ConnectionState;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/actions/remote.dart';
|
||||
import 'package:swift_control/utils/requirements/multi.dart';
|
||||
import 'package:swift_control/utils/requirements/platform.dart';
|
||||
import 'package:swift_control/widgets/small_progress_indicator.dart';
|
||||
|
||||
@@ -18,11 +19,28 @@ bool _isServiceAdded = false;
|
||||
bool _isSubscribedToEvents = false;
|
||||
|
||||
class RemoteRequirement extends PlatformRequirement {
|
||||
RemoteRequirement() : super('Connect to your other device');
|
||||
RemoteRequirement()
|
||||
: super(
|
||||
'Connect to your target device',
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> call(BuildContext context, VoidCallback onUpdate) async {}
|
||||
|
||||
@override
|
||||
Widget? buildDescription() {
|
||||
return settings.getLastTarget() == null
|
||||
? null
|
||||
: Text(
|
||||
switch (settings.getLastTarget()) {
|
||||
Target.iPad =>
|
||||
'On your iPad go to Settings > Accessibility > Touch > AssistiveTouch > Pointer Devices > Devices and pair your device. Make sure AssistiveTouch is enabled.',
|
||||
_ =>
|
||||
'On your ${settings.getLastTarget()?.title} go into Bluetooth settings and look for SwiftControl or your machines name. Pairing is required to use the remote feature.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reconnect() async {
|
||||
await peripheralManager.stopAdvertising();
|
||||
await peripheralManager.removeAllServices();
|
||||
@@ -74,8 +92,7 @@ class RemoteRequirement extends PlatformRequirement {
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (peripheralManager.state != BluetoothLowEnergyState.poweredOn &&
|
||||
peripheralManager.state != BluetoothLowEnergyState.unknown) {
|
||||
while (peripheralManager.state != BluetoothLowEnergyState.poweredOn) {
|
||||
print('Waiting for peripheral manager to be powered on...');
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
@@ -284,6 +301,7 @@ class _PairWidgetState extends State<_PairWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: 10,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 10,
|
||||
@@ -295,21 +313,9 @@ class _PairWidgetState extends State<_PairWidget> {
|
||||
child: Text(_isAdvertising ? 'Stop Pairing' : 'Start Pairing'),
|
||||
),
|
||||
if (_isAdvertising || _isLoading) SizedBox(height: 20, width: 20, child: SmallProgressIndicator()),
|
||||
if (kDebugMode && !screenshotMode)
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
(actionHandler as RemoteActions).sendAbsMouseReport(0, 90, 90);
|
||||
(actionHandler as RemoteActions).sendAbsMouseReport(1, 90, 90);
|
||||
(actionHandler as RemoteActions).sendAbsMouseReport(0, 90, 90);
|
||||
},
|
||||
child: Text('Test'),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_isAdvertising) ...[
|
||||
Text(
|
||||
'If your other device is an iOS device, go to Settings > Accessibility > Touch > AssistiveTouch > Pointer Devices > Devices and pair your device. Make sure AssistiveTouch is enabled.',
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md')));
|
||||
|
||||
@@ -4,8 +4,11 @@ import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:swift_control/utils/keymap/apps/supported_app.dart';
|
||||
import 'package:swift_control/utils/requirements/multi.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
import '../actions/desktop.dart';
|
||||
import '../keymap/apps/custom_app.dart';
|
||||
|
||||
class Settings {
|
||||
@@ -13,6 +16,12 @@ class Settings {
|
||||
|
||||
Future<void> init() async {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
initializeActions(settings.getLastTarget() == Target.thisDevice);
|
||||
|
||||
if (actionHandler is DesktopActions) {
|
||||
// Must add this line.
|
||||
await windowManager.ensureInitialized();
|
||||
}
|
||||
|
||||
try {
|
||||
// Get screen size for migrations
|
||||
@@ -136,6 +145,16 @@ class Settings {
|
||||
return prefs.getString('last_seen_version');
|
||||
}
|
||||
|
||||
Target? getLastTarget() {
|
||||
final targetString = prefs.getString('last_target');
|
||||
if (targetString == null) return null;
|
||||
return Target.values.firstOrNullWhere((e) => e.name == targetString);
|
||||
}
|
||||
|
||||
Future<void> setLastTarget(Target target) async {
|
||||
await prefs.setString('last_target', target.name);
|
||||
}
|
||||
|
||||
Future<void> setLastSeenVersion(String version) async {
|
||||
await prefs.setString('last_seen_version', version);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,29 @@ List<Widget> buildMenuButtons() {
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
PopupMenuButton(
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
child: Text('Troubleshooting Guide'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md')),
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Text('Get Support'),
|
||||
onTap: () {
|
||||
launchUrlString('https://github.com/jonasbark/swiftcontrol/issues');
|
||||
},
|
||||
),
|
||||
];
|
||||
},
|
||||
icon: Icon(Icons.help_outline),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
const MenuButton(),
|
||||
SizedBox(width: 8),
|
||||
];
|
||||
@@ -106,21 +129,7 @@ class MenuButton extends StatelessWidget {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'CHANGELOG.md')));
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Text('Troubleshooting Guide'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md')),
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Text('Feedback'),
|
||||
onTap: () {
|
||||
launchUrlString('https://github.com/jonasbark/swiftcontrol/issues');
|
||||
},
|
||||
),
|
||||
|
||||
PopupMenuItem(
|
||||
child: Text('License'),
|
||||
onTap: () {
|
||||
|
||||
Reference in New Issue
Block a user