Skip to content
5 changes: 5 additions & 0 deletions commet/lib/config/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -560,4 +560,9 @@ class Preferences {

StringListPreference expandedSpaceGroups =
StringListPreference("expanded_space_groups", defaultValue: []);

DoublePreference playerVolume =
DoublePreference("player_volume", defaultValue: 100);
BoolPreference isPlayerMuted =
BoolPreference("is_player_muted", defaultValue: false);
}
94 changes: 94 additions & 0 deletions commet/lib/ui/atoms/hover_menu.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import 'package:commet/utils/debounce.dart';
import 'package:flutter/material.dart';

class HoverMenu extends StatefulWidget {
const HoverMenu(
{super.key,
this.menuAlignment = Alignment.topLeft,
this.parentAlignment = Alignment.topLeft,
this.onHoverStateChanged,
required this.builder,
required this.child});
final Widget child;
final Widget Function(BuildContext context) builder;
final Function(bool hovered)? onHoverStateChanged;
final Alignment menuAlignment;
final Alignment parentAlignment;

@override
State<HoverMenu> createState() => HoverMenuState();
}

class HoverMenuState extends State<HoverMenu> {
OverlayEntry? entry;
LayerLink link = LayerLink();

bool overlayHovered = false;

Debouncer overlayRemoveDebounder =
Debouncer(delay: Duration(milliseconds: 20));

void addOverlay() {
widget.onHoverStateChanged?.call(true);

if (entry != null) {
entry?.remove();
entry = null;
}

entry = OverlayEntry(
canSizeOverlay: true,
builder: (context) {
return Row(
children: [
CompositedTransformFollower(
link: link,
followerAnchor: widget.menuAlignment,
targetAnchor: widget.parentAlignment,
child: MouseRegion(
onEnter: (event) {
overlayRemoveDebounder.cancel();
},
onExit: (event) {
overlayRemoveDebounder.run(removeOverlay);
},
child: widget.builder(context))),
],
);
},
);

Overlay.of(
context,
).insert(entry!);
}

void removeOverlay() {
widget.onHoverStateChanged?.call(false);

if (entry != null) {
entry?.remove();
entry = null;
}
}

@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: link,
child: MouseRegion(
onEnter: (event) {
overlayRemoveDebounder.cancel();
if (entry == null) {
addOverlay();
}
},
onExit: (event) {
if (!overlayHovered) {
overlayRemoveDebounder.run(removeOverlay);
}
},
child: widget.child),
);
}
}
7 changes: 5 additions & 2 deletions commet/lib/ui/atoms/message_attachment.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:math';

import 'package:commet/client/attachment.dart';
import 'package:commet/config/build_config.dart';
import 'package:commet/ui/atoms/lightbox.dart';
Expand Down Expand Up @@ -107,12 +109,13 @@ class _MessageAttachmentState extends State<MessageAttachment> {
bool showInfo =
widget.attachment.name != null && attachment.fileSize != null;

double height = 160;
double height = 200;
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: SizedBox(
height: widget.constrainSize ? height + (showInfo ? 30 : 0) : null,
width: widget.constrainSize ? attachment.aspectRatio * height : null,
width: max(
widget.constrainSize ? attachment.aspectRatio * height : 200, 200),
child: Panel(
mainAxisSize: MainAxisSize.min,
header: showInfo
Expand Down
118 changes: 104 additions & 14 deletions commet/lib/ui/molecules/audio_player/audio_player.dart
Comment thread
Airyzz marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'dart:async';

import 'package:commet/cache/file_provider.dart';
import 'package:commet/ui/atoms/hover_menu.dart';
import 'package:commet/utils/text_utils.dart';
import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import 'package:tiamat/tiamat.dart' as tiamat;
import 'package:commet/main.dart';

class AudioPlayer extends StatefulWidget {
const AudioPlayer(
Expand Down Expand Up @@ -32,9 +34,13 @@ class _AudioPlayerState extends State<AudioPlayer> {
void initState() {
super.initState();

setVolume(preferences.playerVolume.value);
if (preferences.isPlayerMuted.value) toggleIsMuted();

subs = [
player.stream.playing.listen(onPlayingChanged),
player.stream.position.listen(onPositionChanged),
player.stream.volume.listen(onVolumeChanged),
if (widget.file.onProgressChanged != null)
widget.file.onProgressChanged!.listen(onDownloadProgressChanged),
];
Expand All @@ -58,6 +64,12 @@ class _AudioPlayerState extends State<AudioPlayer> {
double displayPosition = 0;
double? downloadProgress;

bool isMuted = false;
double volume = 100;
double preMuteVolume = 100;

final GlobalKey menuKey = GlobalKey();

@override
Widget build(BuildContext context) {
return Container(
Expand Down Expand Up @@ -141,7 +153,44 @@ class _AudioPlayerState extends State<AudioPlayer> {
onChanged: (value) => setState(() {
displayPosition = value;
}),
))
)),
const SizedBox(width: 8),
HoverMenu(
key: menuKey,
menuAlignment: Alignment.bottomCenter,
parentAlignment: Alignment.topCenter,
child: tiamat.IconButton(
icon: isMuted
? Icons.volume_mute
: volume == 0
? Icons.volume_off
: Icons.volume_up,
onPressed: (() {
toggleIsMuted();
preferences.isPlayerMuted.set(isMuted);
}),
),
builder: (context) {
return RotatedBox(
quarterTurns: 3,
child: Container(
decoration: BoxDecoration(
color: ColorScheme.of(context).surfaceContainer,
borderRadius: BorderRadius.circular(8)),
child: SizedBox(
width: 200,
height: 50,
child: tiamat.Slider(
value: volume / 100,
onChanged: (value) => setVolume(value * 100),
onChangeEnd: (value) =>
preferences.playerVolume.set(preMuteVolume),
),
),
));
},
),
const SizedBox(width: 12),
],
),
],
Expand Down Expand Up @@ -170,6 +219,18 @@ class _AudioPlayerState extends State<AudioPlayer> {
}
}

void onPlayingChanged(bool event) {
if (event) {
setState(() {
state = AudioPlayerState.playing;
});
} else {
setState(() {
state = AudioPlayerState.paused;
});
}
}

void loadAudio() async {
var uri = await widget.file.resolve();

Expand All @@ -183,16 +244,11 @@ class _AudioPlayerState extends State<AudioPlayer> {
});
}

void onPlayingChanged(bool event) {
if (event) {
setState(() {
state = AudioPlayerState.playing;
});
} else {
setState(() {
state = AudioPlayerState.paused;
});
}
void onDownloadProgressChanged(DownloadProgress event) {
print(event);
setState(() {
downloadProgress = event.downloaded.toDouble() / event.total.toDouble();
});
}

void onPositionChanged(Duration event) {
Expand All @@ -208,10 +264,44 @@ class _AudioPlayerState extends State<AudioPlayer> {
}
}

void onDownloadProgressChanged(DownloadProgress event) {
print(event);
void toggleIsMuted() {
if (!isMuted) {
setState(() {
isMuted = true;
preMuteVolume = volume;
volume = 0;
});
player.setVolume(0.0);
} else {
setState(() {
isMuted = false;
volume = preMuteVolume;
});
player.setVolume(volume);
}

if (menuKey.currentState case HoverMenuState state) {
state.entry?.markNeedsBuild();
}
}

void setVolume(double value) {
setState(() {
downloadProgress = event.downloaded.toDouble() / event.total.toDouble();
volume = value;
preMuteVolume = value;
isMuted = false;
});

if (menuKey.currentState case HoverMenuState state) {
state.entry?.markNeedsBuild();
}

player.setVolume(value);
}

void onVolumeChanged(double event) {
setState(() {
volume = event;
});
}
}
Loading
Loading