Fix audio player error handling and add session access level display

This commit is contained in:
Nutcake 2023-05-04 21:54:51 +02:00
parent 4e5ac6a8d4
commit ca198a7cc4
4 changed files with 177 additions and 112 deletions

View file

@ -10,10 +10,11 @@ class Session {
final List<String> tags; final List<String> tags;
final bool headlessHost; final bool headlessHost;
final String hostUsername; final String hostUsername;
final SessionAccessLevel accessLevel;
Session({required this.id, required this.name, required this.sessionUsers, required this.thumbnail, Session({required this.id, required this.name, required this.sessionUsers, required this.thumbnail,
required this.maxUsers, required this.hasEnded, required this.isValid, required this.description, required this.maxUsers, required this.hasEnded, required this.isValid, required this.description,
required this.tags, required this.headlessHost, required this.hostUsername, required this.tags, required this.headlessHost, required this.hostUsername, required this.accessLevel,
}); });
factory Session.fromMap(Map map) { factory Session.fromMap(Map map) {
@ -29,6 +30,7 @@ class Session {
tags: ((map["tags"] as List?) ?? []).map((e) => e.toString()).toList(), tags: ((map["tags"] as List?) ?? []).map((e) => e.toString()).toList(),
headlessHost: map["headlessHost"] ?? false, headlessHost: map["headlessHost"] ?? false,
hostUsername: map["hostUsername"] ?? "", hostUsername: map["hostUsername"] ?? "",
accessLevel: SessionAccessLevel.fromName(map["accessLevel"]),
); );
} }
@ -45,12 +47,39 @@ class Session {
"tags": shallow ? [] : throw UnimplementedError(), "tags": shallow ? [] : throw UnimplementedError(),
"headlessHost": headlessHost, "headlessHost": headlessHost,
"hostUsername": hostUsername, "hostUsername": hostUsername,
"accessLevel": accessLevel.name, // This probably wont work, the API usually expects integers.
}; };
} }
bool get isLive => !hasEnded && isValid; bool get isLive => !hasEnded && isValid;
} }
enum SessionAccessLevel {
unknown,
private,
friends,
friendsOfFriends,
anyone;
static const _readableNamesMap = {
SessionAccessLevel.unknown: "Unknown",
SessionAccessLevel.private: "Private",
SessionAccessLevel.friends: "Contacts",
SessionAccessLevel.friendsOfFriends: "Contacts+",
SessionAccessLevel.anyone: "Anyone",
};
factory SessionAccessLevel.fromName(String? name) {
return SessionAccessLevel.values.firstWhere((element) => element.name.toLowerCase() == name?.toLowerCase(),
orElse: () => SessionAccessLevel.unknown,
);
}
String toReadableString() {
return SessionAccessLevel._readableNamesMap[this] ?? "Unknown";
}
}
class SessionUser { class SessionUser {
final String id; final String id;
final String username; final String username;
@ -61,10 +90,10 @@ class SessionUser {
factory SessionUser.fromMap(Map map) { factory SessionUser.fromMap(Map map) {
return SessionUser( return SessionUser(
id: map["userID"], id: map["userID"] ?? "",
username: map["username"], username: map["username"] ?? "Unknown",
isPresent: map["isPresent"], isPresent: map["isPresent"] ?? false,
outputDevice: map["outputDevice"], outputDevice: map["outputDevice"] ?? 0,
); );
} }
} }

View file

@ -27,125 +27,154 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
void initState() { void initState() {
super.initState(); super.initState();
if (Platform.isAndroid) { if (Platform.isAndroid) {
_audioPlayer.setAudioSource(AudioSource.uri(Uri.parse( _audioPlayer.setUrl(
Aux.neosDbToHttp(AudioClipContent.fromMap(jsonDecode(widget.message.content)).assetUri) Aux.neosDbToHttp(AudioClipContent
))).whenComplete(() => _audioPlayer.setLoopMode(LoopMode.off)); .fromMap(jsonDecode(widget.message.content))
.assetUri),
preload: true).whenComplete(() => _audioPlayer.setLoopMode(LoopMode.off));
} }
} }
Widget _createErrorWidget(String error) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.error_outline, color: Theme
.of(context)
.colorScheme
.error,),
const SizedBox(height: 4,),
Text(error, textAlign: TextAlign.center,
softWrap: true,
maxLines: 3,
style: Theme
.of(context)
.textTheme
.bodySmall
?.copyWith(color: Theme
.of(context)
.colorScheme
.error),
),
],
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!Platform.isAndroid) { if (!Platform.isAndroid) {
return Padding( return _createErrorWidget("Sorry, audio-messages are not\n supported on this platform.");
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error,),
const SizedBox(height: 4,),
Text("Sorry, audio-messages are not\n supported on this platform.", textAlign: TextAlign.center,
softWrap: true,
maxLines: 3,
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.error),
),
],
),
);
} }
return IntrinsicWidth( return IntrinsicWidth(
child: StreamBuilder<PlayerState>( child: StreamBuilder<PlayerState>(
stream: _audioPlayer.playerStateStream, stream: _audioPlayer.playerStateStream,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
final playerState = snapshot.data as PlayerState; final playerState = snapshot.data as PlayerState;
return Column( return Column(
children: [ children: [
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
IconButton( IconButton(
onPressed: () { onPressed: () {
switch (playerState.processingState) { switch (playerState.processingState) {
case ProcessingState.idle: case ProcessingState.idle:
case ProcessingState.loading: case ProcessingState.loading:
case ProcessingState.buffering: case ProcessingState.buffering:
break; break;
case ProcessingState.ready: case ProcessingState.ready:
_audioPlayer.play(); _audioPlayer.play();
break; break;
case ProcessingState.completed: case ProcessingState.completed:
_audioPlayer.seek(Duration.zero); _audioPlayer.seek(Duration.zero);
_audioPlayer.play(); _audioPlayer.play();
break; break;
}}, }
icon: playerState.processingState == ProcessingState.loading },
? const Center(child: CircularProgressIndicator(),) icon: SizedBox(
: Icon((_audioPlayer.duration! - _audioPlayer.position).inMilliseconds < 10 ? Icons.replay width: 24,
: (playerState.playing ? Icons.pause : Icons.play_arrow)), height: 24,
), child: playerState.processingState == ProcessingState.loading
StreamBuilder( ? const Center(child: CircularProgressIndicator(),)
stream: _audioPlayer.positionStream, : Icon(((_audioPlayer.duration ?? Duration.zero) - _audioPlayer.position).inMilliseconds <
builder: (context, snapshot) { 10 ? Icons.replay
_sliderValue = (_audioPlayer.position.inMilliseconds / : (playerState.playing ? Icons.pause : Icons.play_arrow)),
(_audioPlayer.duration?.inMilliseconds ?? 0)).clamp(0, 1); ),
return StatefulBuilder( ),
builder: (context, setState) { StreamBuilder(
return Slider( stream: _audioPlayer.positionStream,
value: _sliderValue, builder: (context, snapshot) {
min: 0.0, _sliderValue = (_audioPlayer.position.inMilliseconds /
max: 1.0, (_audioPlayer.duration?.inMilliseconds ?? 0)).clamp(0, 1);
onChanged: (value) async { return StatefulBuilder(
_audioPlayer.pause(); builder: (context, setState) {
setState(() { return Slider(
_sliderValue = value; value: _sliderValue,
}); min: 0.0,
_audioPlayer.seek(Duration( max: 1.0,
milliseconds: (value * (_audioPlayer.duration?.inMilliseconds ?? 0)).round(), onChanged: (value) async {
)); _audioPlayer.pause();
}, setState(() {
_sliderValue = value;
});
_audioPlayer.seek(Duration(
milliseconds: (value * (_audioPlayer.duration?.inMilliseconds ?? 0)).round(),
));
},
);
}
); );
} }
); )
} ],
) ),
], Row(
), mainAxisSize: MainAxisSize.max,
Row( mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max, children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, const SizedBox(width: 12),
children: [ StreamBuilder(
const SizedBox(width: 12), stream: _audioPlayer.positionStream,
StreamBuilder( builder: (context, snapshot) {
stream: _audioPlayer.positionStream, return Text("${snapshot.data?.format() ?? "??"}/${_audioPlayer.duration?.format() ??
builder: (context, snapshot) { "??"}");
return Text("${snapshot.data?.format() ?? "??"}/${_audioPlayer.duration?.format() ?? "??"}"); }
}
),
const Spacer(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Text(
_dateFormat.format(widget.message.sendTime),
style: Theme
.of(context)
.textTheme
.labelMedium
?.copyWith(color: Colors.white54),
), ),
), const Spacer(),
const SizedBox(width: 4,), Padding(
if (widget.message.senderId == ClientHolder.of(context).apiClient.userId) Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0),
padding: const EdgeInsets.only(right: 12.0), child: Text(
child: MessageStateIndicator(messageState: widget.message.state), _dateFormat.format(widget.message.sendTime),
), style: Theme
], .of(context)
) .textTheme
], .labelMedium
); ?.copyWith(color: Colors.white54),
} else { ),
return const Center(child: CircularProgressIndicator(),); ),
const SizedBox(width: 4,),
if (widget.message.senderId == ClientHolder
.of(context)
.apiClient
.userId) Padding(
padding: const EdgeInsets.only(right: 12.0),
child: MessageStateIndicator(messageState: widget.message.state),
),
],
)
],
);
} else if (snapshot.hasError) {
FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace));
return _createErrorWidget("Failed to load audio-message.");
} else {
return const Center(child: CircularProgressIndicator(),);
}
} }
},
), ),
); );
} }

View file

@ -26,13 +26,19 @@ class MessageSessionInvite extends StatelessWidget {
padding: const EdgeInsets.only(left: 10), padding: const EdgeInsets.only(left: 10),
constraints: const BoxConstraints(maxWidth: 300), constraints: const BoxConstraints(maxWidth: 300),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: Text(sessionInfo.name, maxLines: null, softWrap: true, style: Theme.of(context).textTheme.titleMedium,), child: Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(sessionInfo.name, maxLines: null, softWrap: true, style: Theme.of(context).textTheme.titleMedium,),
),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),

View file

@ -583,6 +583,7 @@ class SessionPopup extends StatelessWidget {
style: Theme.of(context).textTheme.labelMedium, style: Theme.of(context).textTheme.labelMedium,
softWrap: true, softWrap: true,
), ),
Text("Access: ${session.accessLevel.toReadableString()}"),
Text("Users: ${session.sessionUsers.length}", style: Theme.of(context).textTheme.labelMedium), Text("Users: ${session.sessionUsers.length}", style: Theme.of(context).textTheme.labelMedium),
Text("Maximum users: ${session.maxUsers}", style: Theme.of(context).textTheme.labelMedium), Text("Maximum users: ${session.maxUsers}", style: Theme.of(context).textTheme.labelMedium),
Text("Headless: ${session.headlessHost ? "Yes" : "No"}", style: Theme.of(context).textTheme.labelMedium), Text("Headless: ${session.headlessHost ? "Yes" : "No"}", style: Theme.of(context).textTheme.labelMedium),