* Use adaptive refresh indicators
* Add new message count indicator in chat
* Sync pubspec.lock
* Initial migration to material 3
* Restore default bottom sheet style
* Remove FrostyListTile and reset styles
* Use "Clear" button for all search history
* Add `require_trailing_commas` lint rule
* Exclude generated files from linter
* Use ListView for safe area support in bottom sheet
* Add initial dark theme
* Make sizing and units consistent
* Update onbaording spacings & copy
* Migrate to default material 3 buttons
* Add headers to chat details bottom sheet
* Simplify token log in method
* Use bottom sheet for account options
* Remove `AnimateScale` widget
* Tweak search paddings
* Update message spacing divisions
* Upgrade packages
* Update message spacing divisions
* Regenerate splash screens
* Update Inter & add google fonts
* Add initial material 3 themes
* Use adaptive alert dialogs
* Revert to oultined inputs
* Fix tooltip message
* Revert add google_fonts
* Improve themes and various styling
* Convert categories grid to list
* Tweak stream card thumbnail radii
* Tweak settings headers
* Left align floating snackbars
* Tweak search header padding
* Fix profile pics occasionally not rounded
* Use profile pic for settings button if logged in
* Redesign sleep timer
* Use divider theme and reduce thickness
* Use 0 letter spacing for tab bar titles
* Redesign category streams list
* Add border to tooltips
* Increase category card font weight
* Use outlined icon btton for chat user modal
* Reduce profile pic radius in search results
* Fix scrolll to top button safe area
* Add support for reply threads
* Reduce tooltip and snackbar border thickness
* Rebuild reply threads on new messages
* Upgrade packages (iOS)
* Reduce font weight for all chat alerts
* Use tabbars for pageview
* Add dividers everywhere
* Revert "Add dividers everywhere"
This reverts commit 5f0d349fdb.
* Remove fill from inputs
* Add dividers to settings
* Use segmented button for settings list
* Add scrollbar to lists with scrollcontroller
* Redesign badge/emote tooltip
* Remove outline from chat user modal buttons
* Remove divider from reply thread
* Use tap for chatters list and increase font weight
* Constrain emote tooltip width
208 lines
7.2 KiB
Dart
208 lines
7.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
import 'package:frosty/apis/twitch_api.dart';
|
|
import 'package:frosty/screens/home/stream_list/large_stream_card.dart';
|
|
import 'package:frosty/screens/home/stream_list/stream_card.dart';
|
|
import 'package:frosty/screens/home/stream_list/stream_list_store.dart';
|
|
import 'package:frosty/screens/home/top/categories/category_card.dart';
|
|
import 'package:frosty/screens/settings/stores/auth_store.dart';
|
|
import 'package:frosty/screens/settings/stores/settings_store.dart';
|
|
import 'package:frosty/widgets/alert_message.dart';
|
|
import 'package:frosty/widgets/loading_indicator.dart';
|
|
import 'package:frosty/widgets/scroll_to_top_button.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
/// A widget that displays a list of followed or top streams based on the provided [listType].
|
|
/// For a widget that displays the top streams under a category, refer to [CategoryStreams].
|
|
class StreamsList extends StatefulWidget {
|
|
/// The type of list to display.
|
|
final ListType listType;
|
|
|
|
final String? categoryId;
|
|
|
|
/// The scroll controller to use for scroll to top functionality.
|
|
final ScrollController? scrollController;
|
|
|
|
final bool showJumpButton;
|
|
|
|
const StreamsList({
|
|
Key? key,
|
|
required this.listType,
|
|
this.categoryId,
|
|
this.scrollController,
|
|
this.showJumpButton = false,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<StreamsList> createState() => _StreamsListState();
|
|
}
|
|
|
|
class _StreamsListState extends State<StreamsList>
|
|
with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
|
|
late final _listStore = ListStore(
|
|
authStore: context.read<AuthStore>(),
|
|
twitchApi: context.read<TwitchApi>(),
|
|
listType: widget.listType,
|
|
categoryId: widget.categoryId,
|
|
scrollController: widget.scrollController ??
|
|
(widget.showJumpButton ? ScrollController() : null),
|
|
);
|
|
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
}
|
|
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
super.didChangeAppLifecycleState(state);
|
|
|
|
if (state == AppLifecycleState.resumed) {
|
|
_listStore.checkLastTimeRefreshedAndUpdate();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
|
|
_listStore.checkLastTimeRefreshedAndUpdate();
|
|
|
|
return RefreshIndicator.adaptive(
|
|
onRefresh: () async {
|
|
HapticFeedback.lightImpact();
|
|
|
|
await _listStore.refreshStreams();
|
|
|
|
if (_listStore.error != null) {
|
|
final snackBar = SnackBar(
|
|
content: AlertMessage(
|
|
message: _listStore.error!,
|
|
centered: false,
|
|
),
|
|
);
|
|
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
}
|
|
},
|
|
child: Observer(
|
|
builder: (_) {
|
|
Widget? statusWidget;
|
|
|
|
if (_listStore.error != null) {
|
|
statusWidget = AlertMessage(message: _listStore.error!);
|
|
}
|
|
|
|
if (_listStore.streams.isEmpty) {
|
|
if (_listStore.isLoading && _listStore.error == null) {
|
|
statusWidget =
|
|
const LoadingIndicator(subtitle: 'Loading streams...');
|
|
} else {
|
|
statusWidget = AlertMessage(
|
|
message: widget.listType == ListType.followed
|
|
? 'No followed streams'
|
|
: 'No top streams',
|
|
);
|
|
}
|
|
}
|
|
|
|
if (statusWidget != null) {
|
|
return CustomScrollView(
|
|
slivers: [
|
|
SliverFillRemaining(
|
|
child: Center(
|
|
child: statusWidget,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
return Stack(
|
|
alignment: AlignmentDirectional.bottomCenter,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
if (widget.categoryId != null &&
|
|
_listStore.categoryDetails != null)
|
|
CategoryCard(
|
|
category: _listStore.categoryDetails!,
|
|
isTappable: false,
|
|
),
|
|
Expanded(
|
|
child: Scrollbar(
|
|
controller: _listStore.scrollController,
|
|
child: ListView.builder(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
controller: _listStore.scrollController,
|
|
itemCount: _listStore.streams.length,
|
|
itemBuilder: (context, index) {
|
|
if (index > _listStore.streams.length - 10 &&
|
|
_listStore.hasMore) {
|
|
debugPrint('$index ${_listStore.streams.length}');
|
|
|
|
_listStore.getStreams();
|
|
}
|
|
return Observer(
|
|
builder: (context) =>
|
|
context.read<SettingsStore>().largeStreamCard
|
|
? LargeStreamCard(
|
|
key: ValueKey(
|
|
_listStore.streams[index].userId,
|
|
),
|
|
streamInfo: _listStore.streams[index],
|
|
showThumbnail: context
|
|
.read<SettingsStore>()
|
|
.showThumbnails,
|
|
showCategory: widget.categoryId == null,
|
|
)
|
|
: StreamCard(
|
|
key: ValueKey(
|
|
_listStore.streams[index].userId,
|
|
),
|
|
streamInfo: _listStore.streams[index],
|
|
showThumbnail: context
|
|
.read<SettingsStore>()
|
|
.showThumbnails,
|
|
showCategory: widget.categoryId == null,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (widget.showJumpButton)
|
|
Observer(
|
|
builder: (context) => AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 300),
|
|
switchInCurve: Curves.easeOut,
|
|
switchOutCurve: Curves.easeIn,
|
|
child: _listStore.showJumpButton
|
|
? ScrollToTopButton(
|
|
scrollController: _listStore.scrollController!,
|
|
)
|
|
: null,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
_listStore.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|