added cargo files
This commit is contained in:
170
PinePods-0.8.2/mobile/lib/ui/podcast/chapter_selector.dart
Normal file
170
PinePods-0.8.2/mobile/lib/ui/podcast/chapter_selector.dart
Normal file
@@ -0,0 +1,170 @@
|
||||
// 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 'dart:async';
|
||||
|
||||
import 'package:pinepods_mobile/bloc/podcast/audio_bloc.dart';
|
||||
import 'package:pinepods_mobile/entities/chapter.dart';
|
||||
import 'package:pinepods_mobile/entities/episode.dart';
|
||||
import 'package:pinepods_mobile/ui/widgets/platform_progress_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
/// A [Widget] for displaying a list of Podcast chapters for those
|
||||
/// podcasts that support that chapter tag.
|
||||
// ignore: must_be_immutable
|
||||
class ChapterSelector extends StatefulWidget {
|
||||
final ItemScrollController itemScrollController = ItemScrollController();
|
||||
Episode episode;
|
||||
Chapter? chapter;
|
||||
StreamSubscription? positionSubscription;
|
||||
var chapters = <Chapter>[];
|
||||
|
||||
ChapterSelector({
|
||||
super.key,
|
||||
required this.episode,
|
||||
}) {
|
||||
chapters = episode.chapters.where((c) => c.toc).toList(growable: false);
|
||||
}
|
||||
|
||||
@override
|
||||
State<ChapterSelector> createState() => _ChapterSelectorState();
|
||||
}
|
||||
|
||||
class _ChapterSelectorState extends State<ChapterSelector> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final audioBloc = Provider.of<AudioBloc>(context, listen: false);
|
||||
Chapter? lastChapter;
|
||||
bool first = true;
|
||||
|
||||
// Listen for changes in position. If the change in position results in
|
||||
// a change in chapter we scroll to it. This ensures that the current
|
||||
// chapter is always visible.
|
||||
// TODO: Jump only if current chapter is not visible.
|
||||
widget.positionSubscription = audioBloc.playPosition!.listen((event) {
|
||||
var episode = event.episode;
|
||||
|
||||
if (widget.itemScrollController.isAttached) {
|
||||
lastChapter ??= episode!.currentChapter;
|
||||
|
||||
if (lastChapter != episode!.currentChapter) {
|
||||
lastChapter = episode.currentChapter;
|
||||
|
||||
if (!episode.chaptersLoading && episode.chapters.isNotEmpty) {
|
||||
var index = widget.chapters.indexWhere((element) => element == lastChapter);
|
||||
|
||||
if (index >= 0) {
|
||||
if (first) {
|
||||
widget.itemScrollController.jumpTo(index: index);
|
||||
first = false;
|
||||
}
|
||||
// Removed auto-scroll to current chapter during playback
|
||||
// to prevent annoying bouncing behavior
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final audioBloc = Provider.of<AudioBloc>(context);
|
||||
|
||||
return StreamBuilder<Episode?>(
|
||||
stream: audioBloc.nowPlaying,
|
||||
builder: (context, snapshot) {
|
||||
return !snapshot.hasData || snapshot.data!.chaptersLoading
|
||||
? const Align(
|
||||
alignment: Alignment.center,
|
||||
child: PlatformProgressIndicator(),
|
||||
)
|
||||
: ScrollablePositionedList.builder(
|
||||
initialScrollIndex: _initialIndex(snapshot.data),
|
||||
itemScrollController: widget.itemScrollController,
|
||||
itemCount: widget.chapters.length,
|
||||
itemBuilder: (context, i) {
|
||||
final index = i < 0 ? 0 : i;
|
||||
final chapter = widget.chapters[index];
|
||||
final chapterSelected = chapter == snapshot.data!.currentChapter;
|
||||
final textStyle = Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
);
|
||||
|
||||
/// We should be able to use the selectedTileColor property but, if we do, when
|
||||
/// we scroll the currently selected item out of view, the selected colour is
|
||||
/// still visible behind the transport control. This is a little hack, but fixes
|
||||
/// the issue until I can get ListTile to work correctly.
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(4.0, 0.0, 4.0, 0.0),
|
||||
child: ListTile(
|
||||
selectedTileColor: Theme.of(context).cardTheme.color,
|
||||
onTap: () {
|
||||
audioBloc.transitionPosition(chapter.startTime);
|
||||
},
|
||||
selected: chapterSelected,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
'${index + 1}.',
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
widget.chapters[index].title.trim(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
maxLines: 3,
|
||||
style: textStyle,
|
||||
),
|
||||
trailing: Text(
|
||||
_formatStartTime(widget.chapters[index].startTime),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.positionSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
int _initialIndex(Episode? e) {
|
||||
var init = 0;
|
||||
|
||||
if (e != null && e.currentChapter != null) {
|
||||
init = widget.chapters.indexWhere((c) => c == e.currentChapter);
|
||||
|
||||
if (init < 0) {
|
||||
init = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return init;
|
||||
}
|
||||
|
||||
String _formatStartTime(double startTime) {
|
||||
var time = Duration(seconds: startTime.ceil());
|
||||
var result = '';
|
||||
|
||||
if (time.inHours > 0) {
|
||||
result =
|
||||
'${time.inHours}:${time.inMinutes.remainder(60).toString().padLeft(2, '0')}:${time.inSeconds.remainder(60).toString().padLeft(2, '0')}';
|
||||
} else {
|
||||
result = '${time.inMinutes}:${time.inSeconds.remainder(60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user