mirror of
https://github.com/feeddeck/feeddeck.git
synced 2026-03-11 17:47:47 -05:00
We always loaded 51 items per fetch instead of 50 items, because we used a range of 0 to 50 which includes the item at position 0 and at position 50. This is now fixed by adjusting the range to 0 to 49, so that exactly 50 items are loaded per fetch request. This commit also improves the number of loaded items shown in the column header. If the status of the ItemsRepository is "loaded" we add a small "+" symbol behind the number to indicat that there are more items, which could be loaded. If the last page was already fetched (status == "loadedLast") then the "+" symbol isn't shown.
232 lines
7.9 KiB
Dart
232 lines
7.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:feeddeck/models/column.dart';
|
|
import 'package:feeddeck/models/source.dart';
|
|
import 'package:feeddeck/repositories/items_repository.dart';
|
|
import 'package:feeddeck/utils/constants.dart';
|
|
import 'package:feeddeck/widgets/column/header/column_layout_header_settings.dart';
|
|
import 'package:feeddeck/widgets/source/source_icon.dart';
|
|
|
|
/// The [ColumnLayoutHeader] widget is the header of a single column. It is used
|
|
/// to display the name of a column, a icon and some actions. The actions which
|
|
/// are currently available are:
|
|
///
|
|
/// - Reload all items in the column
|
|
/// - Mark all items as read / unread
|
|
/// - Show / hide settings of the column
|
|
class ColumnLayoutHeader extends StatefulWidget {
|
|
const ColumnLayoutHeader({
|
|
super.key,
|
|
required this.column,
|
|
required this.openDrawer,
|
|
});
|
|
|
|
final FDColumn column;
|
|
final void Function(Widget widget)? openDrawer;
|
|
|
|
@override
|
|
State<ColumnLayoutHeader> createState() => _ColumnLayoutHeaderState();
|
|
}
|
|
|
|
class _ColumnLayoutHeaderState extends State<ColumnLayoutHeader> {
|
|
bool _isLoadingMarkAllAsReadUnread = false;
|
|
double? _showSettings = 0.0;
|
|
|
|
/// [_markAllAsReadUnread] is called to mark all items in a column as read /
|
|
/// unread.
|
|
///
|
|
/// When the column contains at least one item with `isRead` set to `false` it
|
|
/// will mark all items which as read.
|
|
///
|
|
/// When the column contains no unread items (`isRead == true`) it will mark
|
|
/// all read items as unread.
|
|
Future<void> _markAllAsReadUnread() async {
|
|
setState(() {
|
|
_isLoadingMarkAllAsReadUnread = true;
|
|
});
|
|
|
|
try {
|
|
ItemsRepository items =
|
|
Provider.of<ItemsRepository>(context, listen: false);
|
|
|
|
if (items.items
|
|
.where((item) => item.isRead == false)
|
|
.toList()
|
|
.isNotEmpty) {
|
|
await items.updateReadStates(
|
|
items.items
|
|
.where((item) => item.isRead == false)
|
|
.map((item) => item.id)
|
|
.toList(),
|
|
true,
|
|
);
|
|
} else {
|
|
await items.updateReadStates(
|
|
items.items
|
|
.where((item) => item.isRead == true)
|
|
.map((item) => item.id)
|
|
.toList(),
|
|
false,
|
|
);
|
|
}
|
|
} catch (_) {}
|
|
|
|
setState(() {
|
|
_isLoadingMarkAllAsReadUnread = false;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
ItemsRepository items = Provider.of<ItemsRepository>(context, listen: true);
|
|
|
|
return Column(
|
|
children: [
|
|
AppBar(
|
|
automaticallyImplyLeading: false,
|
|
leading: Padding(
|
|
padding: const EdgeInsets.all(
|
|
Constants.spacingSmall + Constants.spacingExtraSmall,
|
|
),
|
|
child: SourceIcon(
|
|
type: items.column.sources.isNotEmpty
|
|
? items.column.sources[0].type
|
|
: FDSourceType.none,
|
|
icon: items.column.sources.isNotEmpty
|
|
? items.column.sources[0].icon
|
|
: null,
|
|
size: 36,
|
|
),
|
|
),
|
|
centerTitle: false,
|
|
backgroundColor: Constants.background,
|
|
title: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
Characters(items.column.name)
|
|
.replaceAll(
|
|
Characters(''),
|
|
Characters('\u{200B}'),
|
|
)
|
|
.toString(),
|
|
textAlign: TextAlign.left,
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
Text(
|
|
Characters(
|
|
'${items.column.sources.length} ${items.column.sources.length == 1 ? 'Source' : 'Sources'} / ${items.items.length}${items.status == ItemsStatus.loaded ? '+' : ''} ${items.items.length == 1 ? 'Item' : 'Items'}',
|
|
)
|
|
.replaceAll(
|
|
Characters(''),
|
|
Characters('\u{200B}'),
|
|
)
|
|
.toString(),
|
|
textAlign: TextAlign.left,
|
|
style: const TextStyle(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.normal,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
shape: const Border(
|
|
bottom: BorderSide(
|
|
color: Constants.dividerColor,
|
|
width: 1,
|
|
),
|
|
),
|
|
actions: [
|
|
/// The reload button is used to reload all items via the `reload`
|
|
/// function of the [ItemsRepository]. This will also reset all user
|
|
/// defined filters.
|
|
IconButton(
|
|
icon: items.status == ItemsStatus.loading
|
|
? Container(
|
|
width: 20,
|
|
height: 20,
|
|
padding: const EdgeInsets.all(2.0),
|
|
child: const CircularProgressIndicator(
|
|
color: Colors.white,
|
|
strokeWidth: 3,
|
|
),
|
|
)
|
|
: const Icon(Icons.refresh, size: 20.0),
|
|
onPressed: items.status == ItemsStatus.loading
|
|
? null
|
|
: () => items.reload(),
|
|
),
|
|
|
|
/// The mark all items as read / unread will call the
|
|
/// [_markAllAsReadUnread] function to mark all items as read or
|
|
/// unread. If the list contains a unread items we display the
|
|
/// [Icons.visibility] icon, if the column contains at lead one read
|
|
/// item we will display the [Icons.visibility_off] icon. By default
|
|
/// the [Icons.visibility] icon is displayed.
|
|
IconButton(
|
|
icon: items.items
|
|
.where((item) => item.isRead == false)
|
|
.toList()
|
|
.isNotEmpty
|
|
? const Icon(
|
|
Icons.visibility,
|
|
size: 20.0,
|
|
)
|
|
: items.items
|
|
.where((item) => item.isRead == true)
|
|
.toList()
|
|
.isNotEmpty
|
|
? const Icon(
|
|
Icons.visibility_off,
|
|
size: 20.0,
|
|
)
|
|
: const Icon(
|
|
Icons.visibility,
|
|
size: 20.0,
|
|
),
|
|
onPressed: items.items.isEmpty || _isLoadingMarkAllAsReadUnread
|
|
? null
|
|
: () => _markAllAsReadUnread(),
|
|
),
|
|
|
|
/// The settings button is used to show / hide the settings for the
|
|
/// column. To display the settings the [_showSettings] state must
|
|
/// be set to `null`. To hide the settings the state must be set to
|
|
/// `0.0`.
|
|
IconButton(
|
|
icon: const Icon(Icons.settings, size: 20.0),
|
|
onPressed: () {
|
|
setState(() {
|
|
_showSettings = _showSettings == 0.0 ? null : 0.0;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
AnimatedSize(
|
|
duration: const Duration(milliseconds: 300),
|
|
child: Container(
|
|
width: double.infinity,
|
|
height: _showSettings,
|
|
decoration: const BoxDecoration(
|
|
color: Constants.backgroundContainerBackgroundColor,
|
|
),
|
|
padding: const EdgeInsets.all(Constants.spacingMiddle),
|
|
child: ColumnLayoutHeaderSettings(
|
|
column: widget.column,
|
|
openDrawer: widget.openDrawer,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|