import 'package:aves/model/entry.dart'; import 'package:aves/ref/languages.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'aves_dialog.dart'; class VideoStreamSelectionDialog extends StatefulWidget { final Map streams; const VideoStreamSelectionDialog({ Key? key, required this.streams, }) : super(key: key); @override _VideoStreamSelectionDialogState createState() => _VideoStreamSelectionDialogState(); } class _VideoStreamSelectionDialogState extends State { late List _videoStreams, _audioStreams, _textStreams; StreamSummary? _currentVideo, _currentAudio, _currentText; @override void initState() { super.initState(); final byType = groupBy(widget.streams.keys, (v) => v.type); // check width/height to exclude image streams (that are included among video streams) _videoStreams = (byType[StreamType.video] ?? []).where((v) => v.width != null && v.height != null).toList(); _audioStreams = (byType[StreamType.audio] ?? []); _textStreams = (byType[StreamType.text] ?? [])..insert(0, null); final streamEntries = widget.streams.entries; _currentVideo = streamEntries.firstWhereOrNull((kv) => kv.key.type == StreamType.video && kv.value)?.key; _currentAudio = streamEntries.firstWhereOrNull((kv) => kv.key.type == StreamType.audio && kv.value)?.key; _currentText = streamEntries.firstWhereOrNull((kv) => kv.key.type == StreamType.text && kv.value)?.key; } @override Widget build(BuildContext context) { final canSelectVideo = _videoStreams.length > 1; final canSelectAudio = _audioStreams.length > 1; final canSelectText = _textStreams.length > 1; final canSelect = canSelectVideo || canSelectAudio || canSelectText; return AvesDialog( context: context, content: canSelect ? null : Text(context.l10n.videoStreamSelectionDialogNoSelection), scrollableContent: canSelect ? [ if (canSelectVideo) ..._buildSection( icon: AIcons.streamVideo, title: context.l10n.videoStreamSelectionDialogVideo, streams: _videoStreams, current: _currentVideo, setter: (v) => _currentVideo = v, ), if (canSelectAudio) ..._buildSection( icon: AIcons.streamAudio, title: context.l10n.videoStreamSelectionDialogAudio, streams: _audioStreams, current: _currentAudio, setter: (v) => _currentAudio = v, ), if (canSelectText) ..._buildSection( icon: AIcons.streamText, title: context.l10n.videoStreamSelectionDialogText, streams: _textStreams, current: _currentText, setter: (v) => _currentText = v, ), const SizedBox(height: 8), ] : null, actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text(MaterialLocalizations.of(context).cancelButtonLabel), ), if (canSelect) TextButton( onPressed: () => _submit(context), child: Text(context.l10n.applyButtonLabel), ), ], ); } static String _formatLanguage(String value) { final language = Language.living639_2.firstWhereOrNull((language) => language.iso639_2 == value); return language?.native ?? value; } String _commonStreamName(StreamSummary? stream) { if (stream == null) return context.l10n.videoStreamSelectionDialogOff; final title = stream.title; final language = stream.language; if (language != null && language != 'und') { final formattedLanguage = _formatLanguage(language); return '$formattedLanguage${title != null && title != formattedLanguage ? ' • $title' : ''}'; } else if (title != null) { return title; } else { return '${context.l10n.videoStreamSelectionDialogTrack} ${stream.index} (${stream.codecName})'; } } String _streamName(StreamSummary? stream) { final common = _commonStreamName(stream); if (stream != null && stream.type == StreamType.video) { final w = stream.width; final h = stream.height; if (w != null && h != null) { return '$common • $w${AvesEntry.resolutionSeparator}$h'; } } return common; } DropdownMenuItem _buildMenuItem(StreamSummary? value) { return DropdownMenuItem( value: value, child: Text(_streamName(value)), ); } Widget _buildSelectedItem(StreamSummary? v) { return Align( alignment: AlignmentDirectional.centerStart, child: Text( _streamName(v), softWrap: false, overflow: TextOverflow.fade, maxLines: 1, ), ); } List _buildSection({ required IconData icon, required String title, required List streams, required StreamSummary? current, required ValueSetter setter, }) { return [ Padding( padding: const EdgeInsets.only(left: 16, top: 16, right: 16), child: Row( children: [ Icon(icon), const SizedBox(width: 16), Text(title), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: DropdownButton( items: streams.map(_buildMenuItem).toList(), selectedItemBuilder: (context) => streams.map(_buildSelectedItem).toList(), value: current, onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null, isExpanded: true, // use a different shade to avoid having the same background // on the dialog (using the theme `dialogBackgroundColor`) // and on the dropdown (using the theme `canvasColor`) dropdownColor: Colors.grey.shade800, ), ), ]; } void _submit(BuildContext context) => Navigator.pop(context, { StreamType.video: _currentVideo, StreamType.audio: _currentAudio, StreamType.text: _currentText, }); }