import 'package:flutter/material.dart'; import 'package:localization/localization.dart'; import 'package:loris/partials/thread.dart'; import '../../business_logic/timeline/timeline.dart' as tl; import '../../global.dart' as global; class Timeline extends StatefulWidget { const Timeline({Key? key}) : super(key: key); @override State createState() => TimelineState(); } class TimelineState extends State { String? oldestId; final controller = ScrollController(); List children = []; bool loading = false; tl.TimelineType selectedTimelineType = tl.TimelineType.home; String selectedId = global.settings!.activeIdentity; @override void initState() { print("init"); super.initState(); children.add(const LoadingBox()); fetchMore(); controller.addListener(() { if (controller.position.maxScrollExtent <= controller.offset + 2 * MediaQuery.of(context).size.height && !loading) { fetchMore(); } }); } Future fetchMore() async { while (loading) { await Future.delayed(const Duration(milliseconds: 500)); } loading = true; final models = await tl.getTimelineFromServer( oldestId, selectedId, selectedTimelineType); if (mounted) { setState(() { children.removeWhere((element) { return element.runtimeType != Thread; }); List threads = []; for (int i = 0; i < models.length; i++) { threads.add(Thread(model: models[i])); } if (models.isNotEmpty) { oldestId = models.last.posts.last.id; } children.addAll(threads); children.add( Row( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton.icon( onPressed: () { fetchMore(); }, icon: const Icon(Icons.more_horiz), label: Text( "load-more".i18n(), ), ) ], ), ); children.add(const LoadingBox()); loading = false; }); } } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { List identities = []; // add identities to dropdown menu for (int i = 0; i < global.settings!.identities.keys.length; i++) { identities.add(DropdownMenuItem( value: global.settings!.identities.keys.toList()[i], child: Text(global.settings!.identities.keys.toList()[i]), )); } selectedId = global.settings!.activeIdentity; return Column( children: [ Container( color: Theme.of(context).colorScheme.surface, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( onPressed: () { reload(); }, icon: const Icon(Icons.refresh), ), DropdownButton( value: selectedId, items: identities, onChanged: (dynamic value) async { setState(() { selectedId = value ?? global.settings!.activeIdentity; global.settings!.saveActiveIdentity(selectedId); reload(); }); }, ), DropdownButton( value: selectedTimelineType, items: [ DropdownMenuItem( value: tl.TimelineType.home, child: RichText( text: TextSpan( text: "${"home-timeline".i18n()} ", children: const [ WidgetSpan( child: Icon(Icons.home), ), ], ), ), ), DropdownMenuItem( value: tl.TimelineType.local, child: RichText( text: TextSpan( text: "${"local-timeline".i18n()} ", children: const [ WidgetSpan( child: Icon(Icons.people), ) ], ), ), ), DropdownMenuItem( value: tl.TimelineType.public, child: RichText( text: TextSpan( text: "${"public-timeline".i18n()} ", children: const [ WidgetSpan( child: Icon(Icons.public), ), ], ), ), ), ], onChanged: (tl.TimelineType? value) { setState(() { selectedTimelineType = value ?? tl.TimelineType.home; reload(); }); }, ) ], ), ), Expanded( child: ListView.separated( physics: const AlwaysScrollableScrollPhysics(), controller: controller, itemBuilder: (context, index) { return children[index]; }, separatorBuilder: (context, index) { return const Divider( color: Colors.transparent, ); }, itemCount: children.length, padding: const EdgeInsets.fromLTRB(24, 0, 24, 64), addAutomaticKeepAlives: false, ), ), ], ); } void reload() async { controller.animateTo( 0, duration: const Duration(seconds: 1), curve: Curves.easeOut, ); if (mounted) { setState(() { children = [const LoadingBox()]; oldestId = null; }); } await _waitForFetchMore(); if (mounted) { setState(() { fetchMore(); }); } } Future _waitForFetchMore() async { while (loading) { await Future.delayed(const Duration(milliseconds: 500)); } } } class LoadingBox extends StatelessWidget { const LoadingBox({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return SizedBox.fromSize( size: Size(double.infinity, MediaQuery.of(context).size.height), child: Center( child: SizedBox.fromSize( size: const Size(128, 128), child: const CircularProgressIndicator(), ), )); } }