mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-20 19:12:04 +00:00
improved calendar
This commit is contained in:
parent
715f077c13
commit
e565d14ef3
7 changed files with 309 additions and 126 deletions
|
|
@ -16,7 +16,8 @@ import 'package:mangayomi/utils/headers.dart';
|
|||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
class CalendarScreen extends ConsumerStatefulWidget {
|
||||
const CalendarScreen({super.key});
|
||||
final ItemType? itemType;
|
||||
const CalendarScreen({super.key, this.itemType});
|
||||
|
||||
@override
|
||||
ConsumerState<CalendarScreen> createState() => _CalendarScreenState();
|
||||
|
|
@ -32,6 +33,7 @@ class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|||
DateTime? _selectedDay;
|
||||
DateTime? _rangeStart;
|
||||
DateTime? _rangeEnd;
|
||||
late ItemType? itemType = widget.itemType ?? ItemType.manga;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -50,129 +52,169 @@ class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final locale = ref.watch(l10nLocaleStateProvider);
|
||||
final data = ref.watch(getCalendarStreamProvider);
|
||||
final data = ref.watch(getCalendarStreamProvider(itemType: itemType));
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.calendar)),
|
||||
body: data.when(
|
||||
data: (data) {
|
||||
if (data.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(l10n.calendar_no_data, textAlign: TextAlign.center),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (_selectedDay != null) {
|
||||
_selectedEntries.value = _getEntriesForDay(_selectedDay!, data);
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_outlined,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
l10n.calendar_info,
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: context.secondaryColor,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_outlined,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
l10n.calendar_info,
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
TableCalendar(
|
||||
firstDay: firstDay,
|
||||
lastDay: lastDay,
|
||||
focusedDay: _focusedDay,
|
||||
locale: locale.toLanguageTag(),
|
||||
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
|
||||
rangeStartDay: _rangeStart,
|
||||
rangeEndDay: _rangeEnd,
|
||||
calendarFormat: _calendarFormat,
|
||||
rangeSelectionMode: _rangeSelectionMode,
|
||||
eventLoader: (day) => _getEntriesForDay(day, data),
|
||||
startingDayOfWeek: StartingDayOfWeek.monday,
|
||||
calendarStyle: const CalendarStyle(outsideDaysVisible: false),
|
||||
onDaySelected: (selectedDay, focusedDay) =>
|
||||
_onDaySelected(selectedDay, focusedDay, data),
|
||||
onRangeSelected: (start, end, focusedDay) =>
|
||||
_onRangeSelected(start, end, focusedDay, data),
|
||||
onFormatChanged: (format) {
|
||||
if (_calendarFormat != format) {
|
||||
setState(() {
|
||||
_calendarFormat = format;
|
||||
});
|
||||
}
|
||||
},
|
||||
onPageChanged: (focusedDay) {
|
||||
_focusedDay = focusedDay;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Expanded(
|
||||
child: ValueListenableBuilder<List<Manga>>(
|
||||
valueListenable: _selectedEntries,
|
||||
builder: (context, value, _) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
CustomSliverGroupedListView<Manga, String>(
|
||||
elements: value,
|
||||
groupBy: (element) {
|
||||
return dateFormat(
|
||||
_selectedDay?.millisecondsSinceEpoch
|
||||
.toString() ??
|
||||
DateTime.now()
|
||||
.add(
|
||||
Duration(
|
||||
days: element.smartUpdateDays!,
|
||||
),
|
||||
)
|
||||
.millisecondsSinceEpoch
|
||||
.toString(),
|
||||
context: context,
|
||||
ref: ref,
|
||||
forHistoryValue: true,
|
||||
useRelativeTimesTamps: false,
|
||||
);
|
||||
},
|
||||
groupSeparatorBuilder: (String groupByValue) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8,
|
||||
left: 12,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"${dateFormat(null, context: context, stringDate: groupByValue, ref: ref, useRelativeTimesTamps: true, showInDaysFuture: true)} - ${dateFormat(null, context: context, stringDate: groupByValue, ref: ref, useRelativeTimesTamps: false)}",
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SegmentedButton(
|
||||
emptySelectionAllowed: true,
|
||||
showSelectedIcon: false,
|
||||
style: TextButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
),
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: ItemType.manga.index,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(l10n.manga),
|
||||
),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ItemType.anime.index,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(l10n.anime),
|
||||
),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ItemType.novel.index,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(l10n.novel),
|
||||
),
|
||||
),
|
||||
],
|
||||
selected: {itemType?.index},
|
||||
onSelectionChanged: (newSelection) {
|
||||
if (newSelection.isNotEmpty &&
|
||||
newSelection.first != null) {
|
||||
setState(() {
|
||||
itemType =
|
||||
ItemType.values[newSelection.first!];
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, element) {
|
||||
return CalendarListTileWidget(
|
||||
manga: element,
|
||||
selectedDay: _selectedDay,
|
||||
);
|
||||
},
|
||||
order: GroupedListOrder.ASC,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
TableCalendar(
|
||||
firstDay: firstDay,
|
||||
lastDay: lastDay,
|
||||
focusedDay: _focusedDay,
|
||||
locale: locale.toLanguageTag(),
|
||||
selectedDayPredicate: (day) =>
|
||||
isSameDay(_selectedDay, day),
|
||||
rangeStartDay: _rangeStart,
|
||||
rangeEndDay: _rangeEnd,
|
||||
calendarFormat: _calendarFormat,
|
||||
rangeSelectionMode: _rangeSelectionMode,
|
||||
eventLoader: (day) => _getEntriesForDay(day, data),
|
||||
startingDayOfWeek: StartingDayOfWeek.monday,
|
||||
calendarStyle: CalendarStyle(
|
||||
outsideDaysVisible: true,
|
||||
weekendTextStyle: TextStyle(color: context.primaryColor),
|
||||
),
|
||||
onDaySelected: (selectedDay, focusedDay) =>
|
||||
_onDaySelected(selectedDay, focusedDay, data),
|
||||
onRangeSelected: (start, end, focusedDay) =>
|
||||
_onRangeSelected(start, end, focusedDay, data),
|
||||
onFormatChanged: (format) {
|
||||
if (_calendarFormat != format) {
|
||||
setState(() {
|
||||
_calendarFormat = format;
|
||||
});
|
||||
}
|
||||
},
|
||||
onPageChanged: (focusedDay) {
|
||||
_focusedDay = focusedDay;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
],
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder<List<Manga>>(
|
||||
valueListenable: _selectedEntries,
|
||||
builder: (context, value, _) {
|
||||
return CustomSliverGroupedListView<Manga, String>(
|
||||
elements: value,
|
||||
groupBy: (element) {
|
||||
return dateFormat(
|
||||
_selectedDay?.millisecondsSinceEpoch.toString() ??
|
||||
DateTime.now()
|
||||
.add(Duration(days: element.smartUpdateDays!))
|
||||
.millisecondsSinceEpoch
|
||||
.toString(),
|
||||
context: context,
|
||||
ref: ref,
|
||||
forHistoryValue: true,
|
||||
useRelativeTimesTamps: false,
|
||||
);
|
||||
},
|
||||
groupSeparatorBuilder: (String groupByValue) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8, left: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"${dateFormat(null, context: context, stringDate: groupByValue, ref: ref, useRelativeTimesTamps: true, showInDaysFuture: true)} - ${dateFormat(null, context: context, stringDate: groupByValue, ref: ref, useRelativeTimesTamps: false)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, element) {
|
||||
return CalendarListTileWidget(
|
||||
manga: element,
|
||||
selectedDay: _selectedDay,
|
||||
);
|
||||
},
|
||||
order: GroupedListOrder.ASC,
|
||||
);
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(child: const SizedBox(height: 15)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
part 'calendar_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Stream<List<Manga>> getCalendarStream(Ref ref) async* {
|
||||
Stream<List<Manga>> getCalendarStream(
|
||||
Ref ref, {
|
||||
ItemType? itemType,
|
||||
}) async* {
|
||||
yield* isar.mangas
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.itemTypeEqualTo(itemType ?? ItemType.manga)
|
||||
.anyOf([
|
||||
Status.ongoing,
|
||||
Status.unknown,
|
||||
|
|
|
|||
|
|
@ -6,23 +6,156 @@ part of 'calendar_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getCalendarStreamHash() => r'9095ecdef36259f84eb6562a2ca9e253f50d4b8d';
|
||||
String _$getCalendarStreamHash() => r'dcdad165b2da2420bafa8b70c4b3a0fb336e5021';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [getCalendarStream].
|
||||
@ProviderFor(getCalendarStream)
|
||||
final getCalendarStreamProvider =
|
||||
AutoDisposeStreamProvider<List<Manga>>.internal(
|
||||
getCalendarStream,
|
||||
name: r'getCalendarStreamProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$getCalendarStreamHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
const getCalendarStreamProvider = GetCalendarStreamFamily();
|
||||
|
||||
/// See also [getCalendarStream].
|
||||
class GetCalendarStreamFamily extends Family<AsyncValue<List<Manga>>> {
|
||||
/// See also [getCalendarStream].
|
||||
const GetCalendarStreamFamily();
|
||||
|
||||
/// See also [getCalendarStream].
|
||||
GetCalendarStreamProvider call({
|
||||
ItemType? itemType,
|
||||
}) {
|
||||
return GetCalendarStreamProvider(
|
||||
itemType: itemType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
GetCalendarStreamProvider getProviderOverride(
|
||||
covariant GetCalendarStreamProvider provider,
|
||||
) {
|
||||
return call(
|
||||
itemType: provider.itemType,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'getCalendarStreamProvider';
|
||||
}
|
||||
|
||||
/// See also [getCalendarStream].
|
||||
class GetCalendarStreamProvider extends AutoDisposeStreamProvider<List<Manga>> {
|
||||
/// See also [getCalendarStream].
|
||||
GetCalendarStreamProvider({
|
||||
ItemType? itemType,
|
||||
}) : this._internal(
|
||||
(ref) => getCalendarStream(
|
||||
ref as GetCalendarStreamRef,
|
||||
itemType: itemType,
|
||||
),
|
||||
from: getCalendarStreamProvider,
|
||||
name: r'getCalendarStreamProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$getCalendarStreamHash,
|
||||
dependencies: GetCalendarStreamFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
GetCalendarStreamFamily._allTransitiveDependencies,
|
||||
itemType: itemType,
|
||||
);
|
||||
|
||||
GetCalendarStreamProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.itemType,
|
||||
}) : super.internal();
|
||||
|
||||
final ItemType? itemType;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<List<Manga>> Function(GetCalendarStreamRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: GetCalendarStreamProvider._internal(
|
||||
(ref) => create(ref as GetCalendarStreamRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
itemType: itemType,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<List<Manga>> createElement() {
|
||||
return _GetCalendarStreamProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is GetCalendarStreamProvider && other.itemType == itemType;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, itemType.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef GetCalendarStreamRef = AutoDisposeStreamProviderRef<List<Manga>>;
|
||||
mixin GetCalendarStreamRef on AutoDisposeStreamProviderRef<List<Manga>> {
|
||||
/// The parameter `itemType` of this provider.
|
||||
ItemType? get itemType;
|
||||
}
|
||||
|
||||
class _GetCalendarStreamProviderElement
|
||||
extends AutoDisposeStreamProviderElement<List<Manga>>
|
||||
with GetCalendarStreamRef {
|
||||
_GetCalendarStreamProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
ItemType? get itemType => (origin as GetCalendarStreamProvider).itemType;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
|
|
|||
|
|
@ -1891,7 +1891,8 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
elevation: 0,
|
||||
),
|
||||
onPressed: () {},
|
||||
onPressed: () =>
|
||||
context.push("/calendarScreen", extra: widget.manga!.itemType),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'update_manga_detail_providers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$updateMangaDetailHash() => r'33c6bd0f1de57e2e839ae695a0301893b9a94624';
|
||||
String _$updateMangaDetailHash() => r'85660b206c2bce558760118936758a261519cad8';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -234,7 +234,10 @@ class RouterNotifier extends ChangeNotifier {
|
|||
name: "playerAdvancedScreen",
|
||||
child: const PlayerAdvancedScreen(),
|
||||
),
|
||||
_genericRoute(name: "calendarScreen", child: const CalendarScreen()),
|
||||
_genericRoute<ItemType?>(
|
||||
name: "calendarScreen",
|
||||
builder: (itemType) => CalendarScreen(itemType: itemType),
|
||||
),
|
||||
_genericRoute<Manga>(
|
||||
name: "migrate",
|
||||
builder: (manga) => MigrationScreen(manga: manga),
|
||||
|
|
|
|||
|
|
@ -69,15 +69,15 @@ String dateFormat(
|
|||
date.isAfter(fiveDaysAgo) ||
|
||||
date.isAfter(sixDaysAgo) ||
|
||||
date.isAfter(aWeekAgo)) {
|
||||
final difference = today.difference(date).inDays;
|
||||
final difference = today.difference(date).inDays.abs();
|
||||
return switch (difference) {
|
||||
1 =>
|
||||
showInDaysFuture
|
||||
? l10n.in_n_day(difference.abs())
|
||||
? l10n.in_n_day(difference)
|
||||
: l10n.n_day_ago(difference),
|
||||
!= 7 =>
|
||||
showInDaysFuture
|
||||
? l10n.in_n_days(difference.abs())
|
||||
? l10n.in_n_days(difference)
|
||||
: l10n.n_days_ago(difference),
|
||||
_ => l10n.a_week_ago,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue