Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 139 additions & 29 deletions desktop/frontend/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class BackendController extends ChangeNotifier {
double currentVolume = 0.0;
double gainValue = 1.0;
bool isAiEnabled = false;
bool isDarkMode = true;
List<String> logs = [];
List<String> devices = [];
String? selectedDevice;
Expand All @@ -56,6 +57,7 @@ class BackendController extends ChangeNotifier {
final prefs = await SharedPreferences.getInstance();
isAiEnabled = prefs.getBool('isAiEnabled') ?? false;
gainValue = prefs.getDouble('gainValue') ?? 1.0;
isDarkMode = prefs.getBool('isDarkMode') ?? true;
selectedDevice = prefs.getString('selectedDevice');

// Apply loaded settings
Expand All @@ -69,6 +71,7 @@ class BackendController extends ChangeNotifier {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isAiEnabled', isAiEnabled);
await prefs.setDouble('gainValue', gainValue);
await prefs.setBool('isDarkMode', isDarkMode);
if (selectedDevice != null) {
await prefs.setString('selectedDevice', selectedDevice!);
}
Expand Down Expand Up @@ -245,6 +248,12 @@ class BackendController extends ChangeNotifier {
notifyListeners();
}

void toggleTheme(bool value) {
isDarkMode = value;
saveSettings();
notifyListeners();
}

void refreshDevices() {
sendCommand("get_devices");
}
Expand Down Expand Up @@ -277,19 +286,90 @@ class _MyAppState extends State<MyApp> with WindowListener {
await windowManager.destroy();
}

ThemeData _buildTheme(bool isDark) {
final isLinux = Platform.isLinux;
final ubuntuOrange = const Color(0xFFE95420);

if (isLinux) {
if (isDark) {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
primaryColor: ubuntuOrange,
scaffoldBackgroundColor: const Color(0xFF300A24), // Ubuntu Dark Aubergine
colorScheme: ColorScheme.dark(
primary: ubuntuOrange,
secondary: ubuntuOrange,
surface: const Color(0xFF3D3D3D),
onSurface: Colors.white,
surfaceContainerHighest: const Color(0xFF4D4D4D),
),
cardTheme: CardThemeData(
color: const Color(0xFF3D3D3D),
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
} else {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
primaryColor: ubuntuOrange,
scaffoldBackgroundColor: const Color(0xFFF7F7F7),
colorScheme: ColorScheme.light(
primary: ubuntuOrange,
secondary: ubuntuOrange,
surface: Colors.white,
onSurface: const Color(0xFF333333),
surfaceContainerHighest: const Color(0xFFEEEEEE),
),
cardTheme: CardThemeData(
color: Colors.white,
elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
}
} else {
// Default Modern Cyan/Purple Theme
if (isDark) {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
scaffoldBackgroundColor: const Color(0xFF121212),
colorScheme: ColorScheme.dark(
primary: Colors.cyanAccent,
secondary: Colors.purpleAccent,
surface: const Color(0xFF1E1E1E),
surfaceContainerHighest: const Color(0xFF2C2C2C),
),
);
} else {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
scaffoldBackgroundColor: const Color(0xFFF5F5F5),
colorScheme: ColorScheme.light(
primary: Colors.cyan,
secondary: Colors.purple,
surface: Colors.white,
surfaceContainerHighest: const Color(0xFFE0E0E0),
),
);
}
}
}

@override
Widget build(BuildContext context) {
final controller = context.watch<BackendController>();

return MaterialApp(
title: 'MicRouter PC',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(useMaterial3: true).copyWith(
scaffoldBackgroundColor: Colors.grey[900],
colorScheme: ColorScheme.dark(
primary: Colors.cyanAccent,
secondary: Colors.purpleAccent,
surface: Colors.grey[850]!,
),
),
themeMode: controller.isDarkMode ? ThemeMode.dark : ThemeMode.light,
theme: _buildTheme(false),
darkTheme: _buildTheme(true),
home: const HomeScreen(),
);
}
Expand All @@ -312,7 +392,7 @@ class _HomeScreenState extends State<HomeScreen> {
children: [
NavigationRail(
selectedIndex: _selectedIndex,
backgroundColor: Colors.black26,
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.5),
labelType: NavigationRailLabelType.all,
onDestinationSelected: (int index) {
setState(() {
Expand All @@ -332,7 +412,7 @@ class _HomeScreenState extends State<HomeScreen> {
),
],
),
const VerticalDivider(thickness: 1, width: 1, color: Colors.white10),
VerticalDivider(thickness: 1, width: 1, color: Theme.of(context).dividerColor.withOpacity(0.1)),

Expanded(
child: _selectedIndex == 0
Expand Down Expand Up @@ -391,11 +471,11 @@ class _RouterViewState extends State<RouterView> {
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 700),
decoration: BoxDecoration(
color: Colors.black45,
color: Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.5),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white12),
border: Border.all(color: Theme.of(context).dividerColor.withOpacity(0.1)),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4))
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4))
]
),
child: ClipRRect(
Expand All @@ -404,8 +484,11 @@ class _RouterViewState extends State<RouterView> {
alignment: Alignment.centerLeft,
widthFactor: controller.currentVolume.clamp(0.0, 1.0),
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(colors: [Colors.cyanAccent, Colors.purpleAccent]),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.secondary,
]),
),
),
),
Expand All @@ -420,7 +503,9 @@ class _RouterViewState extends State<RouterView> {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: controller.status.contains("running") ? Colors.green.withOpacity(0.2) : Colors.white10,
color: controller.status.contains("running")
? Colors.green.withOpacity(0.2)
: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20)
),
child: Text(
Expand All @@ -429,7 +514,9 @@ class _RouterViewState extends State<RouterView> {
letterSpacing: 1.2,
fontSize: 12,
fontWeight: FontWeight.bold,
color: controller.status.contains("running") ? Colors.greenAccent : Colors.grey
color: controller.status.contains("running")
? (Theme.of(context).brightness == Brightness.dark ? Colors.greenAccent : Colors.green[700])
: Theme.of(context).hintColor
)
),
),
Expand Down Expand Up @@ -470,26 +557,30 @@ class _RouterViewState extends State<RouterView> {
),

const Spacer(),
const Divider(color: Colors.white10),
Divider(color: Theme.of(context).dividerColor.withOpacity(0.1)),
const SizedBox(height: 10),

const Text("System Logs:", style: TextStyle(color: Colors.grey, fontSize: 12)),
Text("System Logs:", style: TextStyle(color: Theme.of(context).hintColor, fontSize: 12)),
Container(
margin: const EdgeInsets.only(top: 8),
padding: const EdgeInsets.all(8),
height: 120,
decoration: BoxDecoration(
color: Colors.black87,
color: Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.3),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.white10)
border: Border.all(color: Theme.of(context).dividerColor.withOpacity(0.1))
),
child: ListView.builder(
controller: _scrollController,
itemCount: controller.logs.length,
itemBuilder: (ctx, i) {
return Text(
">> ${controller.logs[i]}",
style: const TextStyle(fontFamily: 'monospace', fontSize: 11, color: Colors.white70),
style: TextStyle(
fontFamily: 'monospace',
fontSize: 11,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7)
),
);
},
),
Expand All @@ -516,7 +607,6 @@ class SettingsView extends StatelessWidget {
const SizedBox(height: 40),

Card(
color: Colors.white.withOpacity(0.05),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
Expand All @@ -536,13 +626,13 @@ class SettingsView extends StatelessWidget {
const SizedBox(height: 10),
DropdownButtonFormField<String>(
value: controller.selectedDevice,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: InputDecoration(
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
filled: true,
fillColor: Colors.black26
fillColor: Theme.of(context).colorScheme.surface
),
dropdownColor: Colors.grey[850],
dropdownColor: Theme.of(context).colorScheme.surface,
isExpanded: true,
hint: const Text("Select a speaker..."),
items: controller.devices.map((String value) {
Expand All @@ -561,11 +651,31 @@ class SettingsView extends StatelessWidget {
const SizedBox(height: 20),

Card(
color: Colors.white.withOpacity(0.05),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
Icon(Icons.dark_mode, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 15),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Dark Mode", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
Text("Switch between light and dark UI", style: TextStyle(fontSize: 12, color: Colors.grey)),
],
),
),
Switch(
value: controller.isDarkMode,
onChanged: (val) => controller.toggleTheme(val),
),
],
),

const Divider(height: 30, color: Colors.white10),
Row(
children: [
const Icon(Icons.auto_awesome, color: Colors.amber),
Expand Down
24 changes: 11 additions & 13 deletions desktop/frontend/test/widget_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,19 @@ import 'package:flutter_test/flutter_test.dart';

import 'package:microuter_pc/main.dart';

import 'package:provider/provider.dart';

void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
testWidgets('Dashboard smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());

// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);

// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
await tester.pumpWidget(
ChangeNotifierProvider(
create: (_) => BackendController(),
child: const MyApp(),
),
);

// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
// Verify that Dashboard is present
expect(find.text('Dashboard'), findsOneWidget);
});
}
17 changes: 17 additions & 0 deletions desktop/frontend/windows/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Generated file. Do not edit.
//

// clang-format off

#include "generated_plugin_registrant.h"

#include <screen_retriever/screen_retriever_plugin.h>
#include <window_manager/window_manager_plugin.h>

void RegisterPlugins(flutter::PluginRegistry* registry) {
ScreenRetrieverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
}
15 changes: 15 additions & 0 deletions desktop/frontend/windows/flutter/generated_plugin_registrant.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//

// clang-format off

#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_

#include <flutter/plugin_registry.h>

// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);

#endif // GENERATED_PLUGIN_REGISTRANT_
2 changes: 2 additions & 0 deletions desktop/frontend/windows/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever
window_manager
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
Expand Down
Loading