feat: ui updates

This commit is contained in:
2023-10-07 01:20:32 +03:00
parent 18292d04b0
commit e40485e6a2
18 changed files with 280 additions and 115 deletions

View File

@@ -1,14 +1,10 @@
import 'package:flutter/widgets.dart';
const newline = '\n';
const cr = '\r';
const lf = '\n';
// const ansiEscapePattern = r'\x1B\[[0-?]*[ -/]*[@-~]';
// final esc = String.fromCharCodes([0xff]);
const esc = '\x1B';
const colorPatternRaw = r'\[\d*m';
const boldByte = 1;
const italicByte = 3;
const underlineByte = 4;
final homeKey = GlobalKey(debugLabel: 'Home');

View File

@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import '../string_utils.dart';
import 'action.dart';
import 'automation.dart';
import 'builtin_command.dart';
class Alias extends Automation {
Alias({
@@ -16,6 +19,8 @@ class Alias extends Automation {
super.group = '',
});
static const IconData iconData = Icons.text_fields;
factory Alias.empty() => Alias(
id: uuid(),
pattern: '',
@@ -68,3 +73,27 @@ class Alias extends Automation {
group: group ?? this.group,
);
}
final builtInAliases = <Alias>[
Alias(
id: 'builtin-alias-lua-long',
pattern: 'lua *',
action: MUDAction('%1', target: MUDActionTarget.script),
),
Alias(
id: 'builtin-alias-lua-short',
pattern: r'[\\]{3}(.*)',
isRegex: true,
action: MUDAction('%1', target: MUDActionTarget.script),
),
Alias(
id: 'builtin-alias-help',
pattern: 'mudhelp',
action: NativeMUDAction(
(store, parent, matches) {
store.echo(BuiltinCommand.help());
},
),
)
];

View File

@@ -0,0 +1,21 @@
class BuiltinCommand {
static help() {
const sep =
'--------------------------------------------------------------------------------';
final year = DateTime.now().year;
return '''
$sep
Mudblock - Help
$sep
Mudblock is a cross-platform MUD client.
See more information at https://github.com/chenasraf/mudblock
$sep
Developed by Chen Asraf
Copyright © $year - All Rights Reserved
$sep
''';
}
}

View File

@@ -258,7 +258,7 @@ class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
@override
Widget build(BuildContext context) {
final curLabel = _currentDirectionIcon(context);
final curLabel = _currentDirectionIcon(context) ?? data.label;
return _listener(
context: context,
child: Container(
@@ -285,6 +285,7 @@ class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
}) {
if (PlatformUtils.isDesktop) {
return Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: _onPointerDown,
onPointerMove: _onPointerMove,
onPointerUp: _onPointerUp,
@@ -305,16 +306,16 @@ class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
);
}
GameButtonLabelData _currentDirectionIcon(BuildContext context) {
GameButtonLabelData? _currentDirectionIcon(BuildContext context) {
switch (_direction) {
case GameButtonInteraction.dragUp:
return data.labelUp ?? data.label;
return data.labelUp;
case GameButtonInteraction.dragDown:
return data.labelDown ?? data.label;
return data.labelDown;
case GameButtonInteraction.dragLeft:
return data.labelLeft ?? data.label;
return data.labelLeft;
case GameButtonInteraction.dragRight:
return data.labelRight ?? data.label;
return data.labelRight;
default:
return data.label;
}

View File

@@ -88,6 +88,7 @@ class GameButtonSet extends StatelessWidget {
class GameButtonSetData {
final String id;
bool enabled;
String name;
GameButtonSetType type;
List<GameButtonData?> buttons;
@@ -105,8 +106,11 @@ class GameButtonSetData {
this.alignment = Alignment.center,
this.spacing = 8,
this.group = '',
this.enabled = true,
});
static const IconData iconData = Icons.gamepad;
factory GameButtonSetData.empty() => GameButtonSetData(
id: uuid(),
name: '',
@@ -116,9 +120,13 @@ class GameButtonSetData {
Size get size => Size(calculateWidth(), calculateHeight());
List<GameButtonData> get nonEmptyButtons =>
buttons.whereType<GameButtonData>().toList();
factory GameButtonSetData.fromJson(Map<String, dynamic> json) =>
GameButtonSetData(
id: json['id'] as String,
enabled: json['enabled'] as bool? ?? true,
name: json['name'] as String,
type: GameButtonSetType.values.firstWhere(
(type) => type.name == json['type'],
@@ -141,6 +149,7 @@ class GameButtonSetData {
Map<String, dynamic> toJson() => <String, dynamic>{
'id': id,
'enabled': enabled,
'name': name,
'type': type.name,
'buttons': buttons.map((button) => button?.toJson()).toList(),
@@ -155,6 +164,7 @@ class GameButtonSetData {
GameButtonSetData copyWith({
String? id,
bool? enabled,
String? name,
GameButtonSetType? type,
List<GameButtonData?>? buttons,
@@ -165,6 +175,7 @@ class GameButtonSetData {
}) =>
GameButtonSetData(
id: id ?? this.id,
enabled: enabled ?? this.enabled,
name: name ?? this.name,
type: type ?? this.type,
buttons: buttons ?? this.buttons,
@@ -262,7 +273,11 @@ final movementPreset = GameButtonSetData(
label: GameButtonLabelData(icon: Icons.keyboard_arrow_up),
pressAction: MUDAction('north'),
),
null,
GameButtonData(
id: uuid(),
label: GameButtonLabelData(icon: Icons.keyboard_double_arrow_up),
pressAction: MUDAction('up'),
),
GameButtonData(
id: uuid(),
label: GameButtonLabelData(icon: Icons.keyboard_arrow_left),
@@ -288,7 +303,11 @@ final movementPreset = GameButtonSetData(
label: GameButtonLabelData(icon: Icons.keyboard_arrow_down),
pressAction: MUDAction('south'),
),
null,
GameButtonData(
id: uuid(),
label: GameButtonLabelData(icon: Icons.keyboard_double_arrow_down),
pressAction: MUDAction('down'),
),
],
);

View File

@@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import '../string_utils.dart';
import 'action.dart';
import 'automation.dart';
@@ -16,6 +18,8 @@ class Trigger extends Automation {
super.group = '',
});
static const IconData iconData = Icons.lightbulb;
factory Trigger.empty() => Trigger(
id: uuid(),
pattern: '',
@@ -68,3 +72,4 @@ class Trigger extends Automation {
group: group ?? this.group,
);
}

View File

@@ -1,8 +1,13 @@
import 'package:flutter/material.dart';
class Variable {
String name;
String value;
Variable(this.name, this.value);
static const IconData iconData = Icons.settings_input_component;
Variable.empty()
: name = '',
value = '';
@@ -25,3 +30,4 @@ class Variable {
value ?? this.value,
);
}

View File

@@ -87,7 +87,7 @@ final routes = <String, Widget Function(BuildContext)>{
},
Paths.home: (context) => HomeScaffold(
builder: (context, _) {
return HomePage(key: homeKey);
return const HomePage();
},
),
};

View File

@@ -51,6 +51,8 @@ class GameStore extends ChangeNotifier {
MUDProfile get currentProfile => _currentProfile!;
get connected => _clientReady && _client.connected;
Future<GameStore> init() async {
debugPrint('GameStore.init');
fillStockProfiles();
@@ -98,6 +100,7 @@ class GameStore extends ChangeNotifier {
final list = await currentProfile.loadAliases();
aliases.clear();
aliases.addAll(list);
aliases.addAll(builtInAliases);
notifyListeners();
debugPrint('Aliases: ${aliases.length}');
}

View File

@@ -21,7 +21,8 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
@override
void initState() {
buttonSet = widget.buttonSet?.copyWith() ?? GameButtonSetData.empty();
buttonSet = widget.buttonSet?.copyWith() ??
GameButtonSetData.empty().copyWith(buttons: [null, null, null]);
super.initState();
}
@@ -30,14 +31,14 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
return Scaffold(
appBar: AppBar(
title: const Text('Button Set'),
// actions: [
// Switch.adaptive(
// value: buttonSet.enabled,
// onChanged: (value) {
// buttonSet.enabled = value;
// },
// )
// ],
actions: [
Switch.adaptive(
value: buttonSet.enabled,
onChanged: (value) {
buttonSet.enabled = value;
},
)
],
),
body: Align(
alignment: Alignment.topCenter,
@@ -51,17 +52,24 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
return ListView(
shrinkWrap: true,
children: [
TextField(
TextFormField(
decoration: const InputDecoration(
labelText: 'Name',
),
controller: TextEditingController(
text: buttonSet.name,
),
initialValue: buttonSet.name,
onChanged: (value) {
buttonSet.name = value;
},
),
TextFormField(
decoration: const InputDecoration(
labelText: 'Group',
),
initialValue: buttonSet.group,
onChanged: (value) {
buttonSet.group = value;
},
),
const SizedBox(height: 16),
DropdownMenu(
label: const Text('Type'),
@@ -75,11 +83,14 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
)
.toList(),
onSelected: (value) {
buttonSet.type = value as GameButtonSetType;
setState(() {
buttonSet.type = value as GameButtonSetType;
});
},
),
const SizedBox(height: 16),
ButtonSetEditor(data: buttonSet),
ButtonSetEditor(
key: Key(buttonSet.type.name), data: buttonSet),
],
);
},
@@ -124,19 +135,21 @@ class _ButtonSetEditorState extends State<ButtonSetEditor> {
return _buildContainer(
context,
(context, index) {
final data = this.data.buttons[index];
final button = data.buttons[index];
return Container(
height: (button?.size ?? GameButtonData.defaultSize) - data.spacing,
width: (button?.size ?? GameButtonData.defaultSize) - data.spacing,
color: Colors.grey,
child: data != null
child: button != null
? FakeGameButton(
label: data.label,
size: data.size ?? GameButtonData.defaultSize,
spacing: this.data.spacing,
label: button.label,
size: button.size ?? GameButtonData.defaultSize,
spacing: data.spacing,
onEdit: () {
showDialog(
context: context,
builder: (context) => ButtonEditorDialog(
data: data,
data: button,
onSave: (data) {
setState(() {
this.data.buttons[index] = data;

View File

@@ -11,7 +11,7 @@ class ButtonSetListPage extends StatelessWidget with GameStoreMixin {
@override
Widget build(BuildContext context) {
return GenericListPage(
title: const Text('ButtonSets'),
title: const Text('Button Sets'),
save: save,
items: storeOf(context).buttonSets,
detailsPath: Paths.buttonSet,
@@ -32,12 +32,11 @@ class ButtonSetListPage extends StatelessWidget with GameStoreMixin {
onSelected: (value) {
switch (value) {
case 'navigation_preset':
// Navigator.pushNamed(
// context,
// Paths.buttonSet,
// arguments: movementPreset,
// );
save(storeOf(context), movementPreset);
Navigator.pushNamed(
context,
Paths.buttonSet,
arguments: movementPreset,
);
break;
}
},
@@ -47,14 +46,14 @@ class ButtonSetListPage extends StatelessWidget with GameStoreMixin {
return ListTile(
key: Key(buttonSet.id),
title: Text(buttonSet.name),
// subtitle: Text(buttonSet.name),
// leading: Switch.adaptive(
// value: buttonSet.enabled,
// onChanged: (value) {
// buttonSet.enabled = value;
// save(store, buttonSet);
// },
// ),
subtitle: Text('${buttonSet.nonEmptyButtons.length} buttons'),
leading: Switch.adaptive(
value: buttonSet.enabled,
onChanged: (value) {
buttonSet.enabled = value;
save(store, buttonSet);
},
),
trailing: PopupMenuButton(
itemBuilder: (context) {
return [

View File

@@ -30,13 +30,11 @@ class GenericListPage<T> extends StatefulWidget with GameStoreMixin {
class _GenericListPageState<T> extends State<GenericListPage<T>>
with GameStoreStateMixin {
List<T> _filteredItems = [];
String _searchTerms = '';
@override
void initState() {
super.initState();
_filteredItems = [...widget.items];
}
@override
@@ -48,6 +46,7 @@ class _GenericListPageState<T> extends State<GenericListPage<T>>
),
body: GameStore.consumer(
builder: (context, store, child) {
final filteredItems = _search();
debugPrint('Generic list rebuild');
return Column(
children: [
@@ -58,15 +57,17 @@ class _GenericListPageState<T> extends State<GenericListPage<T>>
hintText: 'Search',
),
onChanged: (value) {
_search(value);
setState(() {
_searchTerms = value;
});
},
),
),
ListView.builder(
itemCount: _filteredItems.length,
itemCount: filteredItems.length,
shrinkWrap: true,
itemBuilder: (context, i) {
final item = _filteredItems[i];
final item = filteredItems[i];
return widget.itemBuilder(context, store, item);
},
),
@@ -80,23 +81,20 @@ class _GenericListPageState<T> extends State<GenericListPage<T>>
final item = await Navigator.pushNamed(context, widget.detailsPath);
if (item != null) {
await widget.save(store, item as T);
_search(_searchTerms);
setState(() {});
}
},
),
);
}
void _search(String value) {
setState(() {
_searchTerms = value;
_filteredItems = widget.items.where((item) {
final tags = widget.searchTags(item);
final displayName = widget.displayName(item).toLowerCase();
return displayName.contains(value.toLowerCase()) ||
tags.any((tag) => tag.contains(value.toLowerCase()));
}).toList();
});
List<T> _search() {
return widget.items.where((item) {
final tags = widget.searchTags(item);
final displayName = widget.displayName(item).toLowerCase();
return displayName.contains(_searchTerms.toLowerCase()) ||
tags.any((tag) => tag.contains(_searchTerms.toLowerCase()));
}).toList();
}
}

View File

@@ -1,8 +1,12 @@
import 'package:flutter/material.dart';
import 'package:mudblock/core/features/game_button_set.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import '../core/features/alias.dart';
import '../core/features/profile.dart';
import '../core/platform_utils.dart';
import '../core/features/trigger.dart';
import '../core/features/variable.dart';
import '../core/routes.dart';
import '../core/store.dart';
@@ -15,40 +19,47 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Mudblock'),
title: GameStore.consumer(
builder: (context, store, child) => Text(store.connected
? '${store.currentProfile.name} - Mudblock'
: 'Mudblock'),
),
),
body: ChangeNotifierProvider.value(
value: gameStore,
builder: builder,
),
endDrawer: Drawer(
child: Padding(
padding: PlatformUtils.isDesktop
? const EdgeInsets.only(top: 60)
// ? const EdgeInsets.only(top: 0)
: EdgeInsets.zero,
child: ListView(
children: [
ListTile(
title: const Text('Button Sets'),
onTap: () => Navigator.pushNamed(context, Paths.buttons),
child: ListView(
children: [
ListTile(
leading: const Image(
image: AssetImage('assets/images/logo/logo.png'),
width: 32,
height: 32,
),
ListTile(
title: const Text('Aliases'),
onTap: () => Navigator.pushNamed(context, Paths.aliases),
title: const Text('Mudblock'),
subtitle: FutureBuilder<String>(
future: PackageInfo.fromPlatform().then((pkg) => pkg.version),
builder: (context, data) {
final version = data.data ?? '...';
return Text('v$version');
},
),
ListTile(
title: const Text('Triggers'),
onTap: () => Navigator.pushNamed(context, Paths.triggers),
),
ListTile(
title: const Text('Variables'),
onTap: () => Navigator.pushNamed(context, Paths.variables),
),
ListTile(
title: const Text('Profile'),
),
const Divider(),
GameStore.consumer(
builder: (context, store, child) => ListTile(
title: store.connected
? Text(store.currentProfile.name)
: const Text('Not connected'),
subtitle: store.connected
? Text(
'${store.currentProfile.host}:${store.currentProfile.port}',
)
: const Text('-'),
leading: const CircleAvatar(child: Icon(Icons.cable)),
onTap: () async {
final store = storeOf(context);
final updated = await Navigator.pushNamed(
context,
Paths.profile,
@@ -60,23 +71,48 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
}
},
),
ListTile(
title: const Text('Settings'),
onTap: () => Navigator.pushNamed(context, Paths.settings),
),
ListTile(
title: const Text('Disconnect'),
onTap: () async {
await gameStore.disconnect();
if (context.mounted) {
gameStore.connect(context);
}
},
),
],
),
),
ListTile(
title: const Text('Button Sets'),
leading: const Icon(GameButtonSetData.iconData),
onTap: () => Navigator.pushNamed(context, Paths.buttons),
),
ListTile(
title: const Text('Aliases'),
leading: const Icon(Alias.iconData),
onTap: () => Navigator.pushNamed(context, Paths.aliases),
),
ListTile(
title: const Text('Triggers'),
leading: const Icon(Trigger.iconData),
onTap: () => Navigator.pushNamed(context, Paths.triggers),
),
ListTile(
title: const Text('Variables'),
leading: const Icon(Variable.iconData),
onTap: () => Navigator.pushNamed(context, Paths.variables),
),
const Divider(),
ListTile(
title: const Text('Settings'),
leading: const Icon(Icons.settings),
onTap: () => Navigator.pushNamed(context, Paths.settings),
),
ListTile(
title: const Text('Disconnect'),
leading: const Icon(Icons.exit_to_app),
onTap: () async {
Navigator.of(context).pop();
await gameStore.disconnect();
if (context.mounted) {
gameStore.connect(context);
}
},
),
],
),
),
);
}
}

View File

@@ -5,12 +5,14 @@
import FlutterMacOS
import Foundation
import package_info_plus
import path_provider_foundation
import screen_retriever
import shared_preferences_foundation
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))

View File

@@ -1,5 +1,7 @@
PODS:
- FlutterMacOS (1.0.0)
- package_info_plus (0.0.1):
- FlutterMacOS
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -13,6 +15,7 @@ PODS:
DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
@@ -21,6 +24,8 @@ DEPENDENCIES:
EXTERNAL SOURCES:
FlutterMacOS:
:path: Flutter/ephemeral
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
screen_retriever:
@@ -32,6 +37,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126

View File

@@ -176,6 +176,22 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
icons_launcher:
dependency: "direct dev"
description:
@@ -248,6 +264,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
path:
dependency: "direct main"
description:

View File

@@ -46,6 +46,7 @@ dependencies:
meta: ^1.9.1
lua_dardo: ^0.0.5
path_provider: ^2.1.1
package_info_plus: ^4.1.0
# permission_handler: ^11.0.0
dev_dependencies:
@@ -76,6 +77,8 @@ flutter:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- assets/images/logo/logo.png
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
@@ -135,14 +138,14 @@ icons_launcher:
script_runner:
scripts:
- build:apk: flutter build apk
- build:apk: flutter build apk --no-tree-shake-icons
- name: get-version
cmd: dart run btool get packageVersion
suppress_header_output: true
- name: get-name
cmd: dart run btool get packageName
suppress_header_output: true
- push: 'name=$(get-name); version=$(get-version); echo "adb push build/app/outputs/flutter-apk/app-release.apk /sdcard/Download/$name-$version.apk"; adb push build/app/outputs/flutter-apk/app-release.apk "/sdcard/Download/$name-$version.apk"'
- push: 'name=$(get-name); version=$(get-version); echo "adb push $(pwd)/build/app/outputs/flutter-apk/app-release.apk /sdcard/Download/$name-$version.apk"; adb push "$(pwd)/build/app/outputs/flutter-apk/app-release.apk" "/sdcard/Download/$name-$version.apk"'
- install: adb install build/app/outputs/flutter-apk/app-release.apk
- to-device: build:apk && push && install
- auto-fix: dart fix --apply

View File

@@ -1,3 +1,5 @@
// ignore_for_file: avoid_print
import 'dart:io';
void main() async {
@@ -13,20 +15,14 @@ void main() async {
exit(1);
}
// final fileHref = 'https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/icons.dart?raw=true';
final inputFile =
File("$flutterSdk/packages/flutter/lib/src/material/icons.dart");
final output = File('lib/core/icon_list.g.dart');
// final source = await HttpClient().getUrl(Uri.parse(fileHref));
// final content = await source.close();
// final lines = await content.transform(const Utf8Decoder()).transform(const LineSplitter()).toList();
final icons = <String, Set<String>>{};
final lines = await inputFile.readAsLines();
for (final line in lines) {
// example line:
// static const IconData home = IconData(0xe318, fontFamily: 'MaterialIcons');
try {
if (line.contains('static const IconData')) {
final parts = line.trim().split(RegExp(r'\s+'));