added cargo files
This commit is contained in:
317
PinePods-0.8.2/mobile/lib/ui/podcast/now_playing_options.dart
Normal file
317
PinePods-0.8.2/mobile/lib/ui/podcast/now_playing_options.dart
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2020 Ben Hills and the project contributors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:pinepods_mobile/bloc/podcast/queue_bloc.dart';
|
||||
import 'package:pinepods_mobile/bloc/settings/settings_bloc.dart';
|
||||
import 'package:pinepods_mobile/l10n/L.dart';
|
||||
import 'package:pinepods_mobile/state/queue_event_state.dart';
|
||||
import 'package:pinepods_mobile/ui/podcast/transcript_view.dart';
|
||||
import 'package:pinepods_mobile/ui/podcast/up_next_view.dart';
|
||||
import 'package:pinepods_mobile/ui/podcast/pinepods_up_next_view.dart';
|
||||
import 'package:pinepods_mobile/ui/widgets/slider_handle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// This class gives us options that can be dragged up from the bottom of the main player
|
||||
/// window.
|
||||
///
|
||||
/// Currently these options are Up Next & Transcript.
|
||||
///
|
||||
/// This class is an initial version and should by much simpler than it is; however,
|
||||
/// a [NestedScrollView] is the widget we need to implement this UI, there is a current
|
||||
/// issue whereby the scroll view and [DraggableScrollableSheet] clash and therefore cannot
|
||||
/// be used together.
|
||||
///
|
||||
/// See issues [64157](https://github.com/flutter/flutter/issues/64157)
|
||||
/// [67219](https://github.com/flutter/flutter/issues/67219)
|
||||
///
|
||||
/// If anyone can come up with a more elegant solution (and one that does not throw
|
||||
/// an overflow error in debug) please raise and issue/submit a PR.
|
||||
///
|
||||
class NowPlayingOptionsSelector extends StatefulWidget {
|
||||
final double? scrollPos;
|
||||
static const baseSize = 68.0;
|
||||
|
||||
const NowPlayingOptionsSelector({super.key, this.scrollPos});
|
||||
|
||||
@override
|
||||
State<NowPlayingOptionsSelector> createState() => _NowPlayingOptionsSelectorState();
|
||||
}
|
||||
|
||||
class _NowPlayingOptionsSelectorState extends State<NowPlayingOptionsSelector> {
|
||||
DraggableScrollableController? draggableController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final queueBloc = Provider.of<QueueBloc>(context, listen: false);
|
||||
final theme = Theme.of(context);
|
||||
final windowHeight = MediaQuery.of(context).size.height;
|
||||
final minSize = NowPlayingOptionsSelector.baseSize / (windowHeight - NowPlayingOptionsSelector.baseSize);
|
||||
|
||||
return DraggableScrollableSheet(
|
||||
initialChildSize: minSize,
|
||||
minChildSize: minSize,
|
||||
maxChildSize: 1.0,
|
||||
controller: draggableController,
|
||||
// Snap doesn't work as the sheet and scroll controller just don't get along
|
||||
// snap: true,
|
||||
// snapSizes: [minSize, maxSize],
|
||||
builder: (BuildContext context, ScrollController scrollController) {
|
||||
return StreamBuilder<QueueState>(
|
||||
initialData: QueueEmptyState(),
|
||||
stream: queueBloc.queue,
|
||||
builder: (context, queueSnapshot) {
|
||||
final hasTranscript = queueSnapshot.hasData &&
|
||||
queueSnapshot.data?.playing != null &&
|
||||
queueSnapshot.data!.playing!.hasTranscripts;
|
||||
|
||||
return DefaultTabController(
|
||||
animationDuration: !draggableController!.isAttached || draggableController!.size <= minSize
|
||||
? const Duration(seconds: 0)
|
||||
: kTabScrollDuration,
|
||||
length: hasTranscript ? 2 : 1,
|
||||
child: LayoutBuilder(builder: (BuildContext ctx, BoxConstraints constraints) {
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints.expand(
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
child: Material(
|
||||
color: theme.secondaryHeaderColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).highlightColor,
|
||||
width: 0.0,
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(18.0),
|
||||
topRight: Radius.circular(18.0),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SliderHandle(
|
||||
label: optionsSliderOpen()
|
||||
? L.of(context)!.semantic_playing_options_collapse_label
|
||||
: L.of(context)!.semantic_playing_options_expand_label,
|
||||
onTap: () {
|
||||
if (draggableController != null) {
|
||||
if (draggableController!.size < 1.0) {
|
||||
draggableController!.animateTo(
|
||||
1.0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
} else {
|
||||
draggableController!.animateTo(
|
||||
0.0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.0),
|
||||
border: Border(
|
||||
bottom: draggableController != null &&
|
||||
(!draggableController!.isAttached || draggableController!.size <= minSize)
|
||||
? BorderSide.none
|
||||
: BorderSide(color: Colors.grey[800]!, width: 1.0),
|
||||
),
|
||||
),
|
||||
child: TabBar(
|
||||
onTap: (index) {
|
||||
DefaultTabController.of(ctx).animateTo(index);
|
||||
|
||||
if (draggableController != null && draggableController!.size < 1.0) {
|
||||
draggableController!.animateTo(
|
||||
1.0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
},
|
||||
automaticIndicatorColorAdjustment: false,
|
||||
indicatorPadding: EdgeInsets.zero,
|
||||
|
||||
/// Little hack to hide the indicator when closed
|
||||
indicatorColor: draggableController != null &&
|
||||
(!draggableController!.isAttached || draggableController!.size <= minSize)
|
||||
? Theme.of(context).secondaryHeaderColor
|
||||
: null,
|
||||
tabs: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
|
||||
child: Text(
|
||||
L.of(context)!.up_next_queue_label.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
if (hasTranscript)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
|
||||
child: Text(
|
||||
L.of(context)!.transcript_label.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(bottom: 12.0)),
|
||||
Expanded(
|
||||
child: Consumer<SettingsBloc>(
|
||||
builder: (context, settingsBloc, child) {
|
||||
final settings = settingsBloc.currentSettings;
|
||||
final isPinepodsConnected = settings.pinepodsServer != null &&
|
||||
settings.pinepodsApiKey != null &&
|
||||
settings.pinepodsUserId != null;
|
||||
|
||||
return TabBarView(
|
||||
children: [
|
||||
isPinepodsConnected
|
||||
? const PinepodsUpNextView()
|
||||
: const UpNextView(),
|
||||
if (hasTranscript)
|
||||
const TranscriptView(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool optionsSliderOpen() {
|
||||
return (draggableController != null && draggableController!.isAttached && draggableController!.size == 1.0);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
draggableController = DraggableScrollableController();
|
||||
super.initState();
|
||||
}
|
||||
}
|
||||
|
||||
class NowPlayingOptionsScaffold extends StatelessWidget {
|
||||
const NowPlayingOptionsScaffold({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox(
|
||||
height: NowPlayingOptionsSelector.baseSize - 8.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// This implementation displays the additional options in a tab set outside of a
|
||||
/// draggable sheet.
|
||||
///
|
||||
/// Currently these options are Up Next & Transcript.
|
||||
class NowPlayingOptionsSelectorWide extends StatefulWidget {
|
||||
final double? scrollPos;
|
||||
static const baseSize = 68.0;
|
||||
|
||||
const NowPlayingOptionsSelectorWide({super.key, this.scrollPos});
|
||||
|
||||
@override
|
||||
State<NowPlayingOptionsSelectorWide> createState() => _NowPlayingOptionsSelectorWideState();
|
||||
}
|
||||
|
||||
class _NowPlayingOptionsSelectorWideState extends State<NowPlayingOptionsSelectorWide> {
|
||||
DraggableScrollableController? draggableController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final queueBloc = Provider.of<QueueBloc>(context, listen: false);
|
||||
final theme = Theme.of(context);
|
||||
final scrollController = ScrollController();
|
||||
|
||||
return StreamBuilder<QueueState>(
|
||||
initialData: QueueEmptyState(),
|
||||
stream: queueBloc.queue,
|
||||
builder: (context, queueSnapshot) {
|
||||
final hasTranscript = queueSnapshot.hasData &&
|
||||
queueSnapshot.data?.playing != null &&
|
||||
queueSnapshot.data!.playing!.hasTranscripts;
|
||||
|
||||
return DefaultTabController(
|
||||
length: hasTranscript ? 2 : 1,
|
||||
child: LayoutBuilder(builder: (BuildContext ctx, BoxConstraints constraints) {
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints.expand(
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
child: Material(
|
||||
color: theme.secondaryHeaderColor,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.0),
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Colors.grey[800]!, width: 1.0),
|
||||
),
|
||||
),
|
||||
child: TabBar(
|
||||
automaticIndicatorColorAdjustment: false,
|
||||
tabs: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
child: Text(
|
||||
L.of(context)!.up_next_queue_label.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
if (hasTranscript)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
child: Text(
|
||||
L.of(context)!.transcript_label.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
const UpNextView(),
|
||||
if (hasTranscript)
|
||||
const TranscriptView(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user