Files
gomix_flutter/lib/main.dart
minie4 959796210d Implement WebSocket API client
- List ports
- Read and write mute state
- Read and write volume
2024-02-29 11:49:49 +01:00

248 lines
8.4 KiB
Dart

import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:gomix_flutter/mixer_state.dart' as mixer;
import 'package:gomix_flutter/mixing_tab.dart';
import 'package:gomix_flutter/ports_tab.dart';
import 'package:gomix_flutter/settings_tab.dart';
import 'package:gomix_flutter/error_page.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
void main() => runApp(const GoMixClient());
class GoMixClient extends StatelessWidget {
const GoMixClient({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.greenAccent),
sliderTheme: const SliderThemeData(
showValueIndicator: ShowValueIndicator.always,
),
),
// darkTheme: ThemeData(
// useMaterial3: true,
// colorScheme: ColorScheme.fromSeed(
// seedColor: Colors.greenAccent, brightness: Brightness.dark)),
home: const GoMixHome(),
);
}
}
class GoMixHome extends StatefulWidget {
const GoMixHome({super.key});
@override
State<GoMixHome> createState() => _GoMixHomeState();
}
class _GoMixHomeState extends State<GoMixHome> {
int currentPageIndex = 0;
mixer.MixerState mixerState = mixer.MixerState(inputs: [], outputs: []);
WebSocketChannel? ws;
late final SharedPreferences prefs;
late final prefsFuture =
SharedPreferences.getInstance().then((e) => {prefs = e, initWs()});
ValueNotifier<String> connectionError = ValueNotifier("");
void initWs() {
setState(() {
connectionError.value = "";
});
var connections = jsonDecode(prefs.getString("connections") ?? "{}")
as Map<String, dynamic>;
var currentConnection = prefs.getString("selectedConnection");
var wsUrl = (connections[currentConnection] ?? {"url": ""})["url"];
if (wsUrl == "") {
connectionError.value =
"No GoMix connection available! Please add a connection in the server selector.";
return;
}
ws = WebSocketChannel.connect(
Uri.parse(wsUrl),
);
// Listen to incoming websocket messages
ws?.stream.listen((message) {
final data = jsonDecode(message) as Map<String, dynamic>;
// Update complete port list
if ((data["type"] == "response" &&
data["requestMethod"] == "listPorts") ||
data["type"] == "portListChange") {
setState(() {
mixerState =
mixer.mixerStateFromJson(jsonEncode(data["responseData"]));
});
}
// Update a single port
if (data["type"] == "portChange") {
int index = mixerState.inputs.indexWhere(
(element) => element.uuid == data["responseData"]["UUID"]);
int index2 = mixerState.outputs.indexWhere(
(element) => element.uuid == data["responseData"]["UUID"]);
mixer.Port newPort = mixer.Port.fromJson(data["responseData"]);
setState(() {
if (index >= 0) {
mixerState.inputs[index] = newPort;
} else if (index2 >= 0) {
mixerState.outputs[index2] = newPort;
}
});
}
}, onError: (err) {
connectionError.value = err.toString();
});
// Request the current port list
ws?.sink.add(json.encode({"method": "listPorts"}));
// Get notified about port changes
ws?.sink.add(json.encode({"method": "enableUpdates"}));
}
void sendAction(Map<String, Object> data) {
ws?.sink.add(json.encode(data));
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return FutureBuilder(
future: prefsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()));
}
return Scaffold(
bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) {
setState(() {
currentPageIndex = index;
});
},
selectedIndex: currentPageIndex,
indicatorColor: theme.colorScheme.secondaryContainer,
destinations: <Widget>[
NavigationDestination(
icon: Transform.rotate(
angle: 90 * pi / 180,
child: const Icon(Icons.tune_outlined)),
label: 'Mixing',
tooltip: "",
),
const NavigationDestination(
icon: Icon(Icons.settings_input_component_outlined),
label: 'Ports',
tooltip: "",
),
const NavigationDestination(
icon: Icon(Icons.settings_outlined),
label: 'Settings',
tooltip: "",
),
],
),
appBar: AppBar(
title: const Text('go-mix Audio Mixer'),
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.check_circle_outline),
color: theme.colorScheme.inversePrimary,
tooltip: 'Connected',
onPressed: () {},
),
actions: <Widget>[
Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
icon: const Icon(Icons.supervised_user_circle_outlined),
tooltip: 'Select Server',
onPressed: () => showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Select Instance'),
content: const Text("WIP"),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
],
),
),
),
),
],
),
body: Column(children: [
Expanded(
child: FutureBuilder(
future: ws?.ready,
builder: (context, snapshot) {
if (connectionError.value != "") {
return const ErrorPage(
message:
"Could not connect to the GoMix instance!");
}
if (snapshot.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
}
return <Widget>[
MixingTab(
mixerState: mixerState,
sendAction: sendAction,
),
const PortsTab(),
const SettingsTab(),
][currentPageIndex];
}),
),
ValueListenableBuilder(
valueListenable: connectionError,
builder: (ctx, value, child) {
if (connectionError.value == "") {
return const SizedBox();
}
Future.delayed(const Duration(seconds: 0), () {
showDialog(
context: ctx,
builder: (ctx) {
return AlertDialog(
title: const Text('Connection error'),
content: Text(connectionError.value),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Ok'),
),
],
);
});
});
return const SizedBox();
}),
]),
);
});
}
}