mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 06:52:18 +02:00
The Material theme now draws its main surfaces with rounded Material Symbols: navigation, player, mini player, queue, settings, sheets, dialogs and shared controls. AppIcon maps legacy Icons to their symbol and keeps outlined glyphs unfilled. Mornye keeps its original icons. Icon buttons, text and filled buttons, dialog actions, sliders, the setup FAB and indeterminate spinners use the Material 3 Expressive components. Each wrapper falls back to the standard Flutter widget for Mornye, reduced motion and custom sizing, so existing layouts and hit areas stay intact.
117 lines
3.4 KiB
Dart
117 lines
3.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:spotiflac_android/l10n/l10n.dart';
|
|
import 'package:spotiflac_android/widgets/app_icon_button.dart';
|
|
|
|
/// Uses the system's real audio routes and keeps native discovery in native UI.
|
|
class AudioOutputButton extends StatefulWidget {
|
|
const AudioOutputButton({super.key, this.color, this.onPickerChanged});
|
|
|
|
final Color? color;
|
|
final ValueChanged<bool>? onPickerChanged;
|
|
|
|
@override
|
|
State<AudioOutputButton> createState() => _AudioOutputButtonState();
|
|
}
|
|
|
|
class _AudioOutputButtonState extends State<AudioOutputButton> {
|
|
static const _androidChannel = MethodChannel(
|
|
'com.zarz.spotiflac/audio_output',
|
|
);
|
|
MethodChannel? _viewChannel;
|
|
bool _opening = false;
|
|
|
|
Map<String, Object> get _appearance => {
|
|
'color': (widget.color ?? Theme.of(context).colorScheme.onSurface)
|
|
.toARGB32(),
|
|
'label': context.l10n.nowPlayingAudioOutput,
|
|
};
|
|
|
|
@override
|
|
void didUpdateWidget(AudioOutputButton oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
_updateAppearance();
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
_updateAppearance();
|
|
}
|
|
|
|
void _updateAppearance() {
|
|
final channel = _viewChannel;
|
|
if (channel != null) {
|
|
unawaited(channel.invokeMethod<void>('update', _appearance));
|
|
}
|
|
}
|
|
|
|
void _attachView(int id) {
|
|
_viewChannel?.setMethodCallHandler(null);
|
|
_viewChannel = MethodChannel('com.zarz.spotiflac/audio_output/$id')
|
|
..setMethodCallHandler((call) async {
|
|
if (mounted && call.method == 'pickerChanged') {
|
|
widget.onPickerChanged?.call(call.arguments == true);
|
|
}
|
|
});
|
|
_updateAppearance();
|
|
}
|
|
|
|
Future<void> _showAndroidPicker() async {
|
|
if (_opening) return;
|
|
_opening = true;
|
|
widget.onPickerChanged?.call(true);
|
|
var opened = false;
|
|
try {
|
|
opened = await _androidChannel.invokeMethod<bool>('show') ?? false;
|
|
} on PlatformException {
|
|
// Show a visible failure instead of leaving an unresponsive control.
|
|
} on MissingPluginException {
|
|
// The native bridge is not available on unsupported builds.
|
|
} finally {
|
|
_opening = false;
|
|
if (mounted) widget.onPickerChanged?.call(false);
|
|
}
|
|
if (mounted && !opened) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(context.l10n.nowPlayingAudioOutputUnavailable)),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_viewChannel?.setMethodCallHandler(null);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (kIsWeb) return const SizedBox.shrink();
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.iOS:
|
|
return SizedBox.square(
|
|
dimension: 48,
|
|
child: UiKitView(
|
|
viewType: 'com.zarz.spotiflac/audio_output',
|
|
creationParams: _appearance,
|
|
creationParamsCodec: const StandardMessageCodec(),
|
|
onPlatformViewCreated: _attachView,
|
|
),
|
|
);
|
|
case TargetPlatform.android:
|
|
return AppIconButton(
|
|
tooltip: context.l10n.nowPlayingAudioOutput,
|
|
color: widget.color,
|
|
icon: const Icon(Icons.speaker_group_outlined),
|
|
onPressed: _showAndroidPicker,
|
|
);
|
|
default:
|
|
return const SizedBox.shrink();
|
|
}
|
|
}
|
|
}
|