add option to disable auto-scroll. (#225)

This commit is contained in:
Jiaqi Feng
2023-06-05 18:23:29 -07:00
committed by GitHub
parent e33ff417fb
commit 8d238744c7
14 changed files with 159 additions and 153 deletions

BIN
assets/hacki-github.xcf Normal file

Binary file not shown.

BIN
assets/hacki.xcf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

View File

@ -68,6 +68,8 @@ class PreferenceState extends Equatable {
bool get swipeGestureEnabled => _isOn<SwipeGesturePreference>(); bool get swipeGestureEnabled => _isOn<SwipeGesturePreference>();
bool get autoScrollEnabled => _isOn<AutoScrollModePreference>();
List<StoryType> get tabs { List<StoryType> get tabs {
final String result = final String result =
preferences.singleWhereType<TabOrderPreference>().val.toString(); preferences.singleWhereType<TabOrderPreference>().val.toString();

View File

@ -48,3 +48,11 @@ class SearchState extends Equatable {
params, params,
]; ];
} }
extension SearchStateExtension on SearchState {
bool get showDateRangeShortcutChips {
return hasDateFilter &&
dateFilter?.startTime != null &&
dateFilter?.endTime != null;
}
}

View File

@ -30,6 +30,7 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
const StoryUrlModePreference(), const StoryUrlModePreference(),
const NotificationModePreference(), const NotificationModePreference(),
const SwipeGesturePreference(), const SwipeGesturePreference(),
const AutoScrollModePreference(),
const CollapseModePreference(), const CollapseModePreference(),
const ReaderModePreference(), const ReaderModePreference(),
const MarkReadStoriesModePreference(), const MarkReadStoriesModePreference(),
@ -54,12 +55,13 @@ const bool _notificationModeDefaultValue = true;
const bool _swipeGestureModeDefaultValue = false; const bool _swipeGestureModeDefaultValue = false;
const bool _displayModeDefaultValue = true; const bool _displayModeDefaultValue = true;
const bool _eyeCandyModeDefaultValue = false; const bool _eyeCandyModeDefaultValue = false;
const bool _trueDarkModeDefaultValue = false; const bool _trueDarkModeDefaultValue = true;
const bool _readerModeDefaultValue = true; const bool _readerModeDefaultValue = true;
const bool _markReadStoriesModeDefaultValue = true; const bool _markReadStoriesModeDefaultValue = true;
const bool _metadataModeDefaultValue = true; const bool _metadataModeDefaultValue = true;
const bool _storyUrlModeDefaultValue = true; const bool _storyUrlModeDefaultValue = true;
const bool _collapseModeDefaultValue = true; const bool _collapseModeDefaultValue = true;
const bool _autoScrollModeDefaultValue = true;
final int _fetchModeDefaultValue = FetchMode.eager.index; final int _fetchModeDefaultValue = FetchMode.eager.index;
final int _commentsOrderDefaultValue = CommentsOrder.natural.index; final int _commentsOrderDefaultValue = CommentsOrder.natural.index;
final int _fontSizeDefaultValue = FontSize.regular.index; final int _fontSizeDefaultValue = FontSize.regular.index;
@ -127,6 +129,26 @@ class CollapseModePreference extends BooleanPreference {
'''if disabled, tap on the top of comment tile to collapse.'''; '''if disabled, tap on the top of comment tile to collapse.''';
} }
class AutoScrollModePreference extends BooleanPreference {
const AutoScrollModePreference({bool? val})
: super(val: val ?? _autoScrollModeDefaultValue);
@override
AutoScrollModePreference copyWith({required bool? val}) {
return AutoScrollModePreference(val: val);
}
@override
String get key => 'autoScrollMode';
@override
String get title => 'Auto-scroll on collapsing';
@override
String get subtitle =>
'''Automatically scroll to next comment when you collapse a comment.''';
}
/// The value deciding whether or not the story /// The value deciding whether or not the story
/// tile should display link preview. Defaults to true. /// tile should display link preview. Defaults to true.
class DisplayModePreference extends BooleanPreference { class DisplayModePreference extends BooleanPreference {

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_fadein/flutter_fadein.dart'; import 'package:flutter_fadein/flutter_fadein.dart';
import 'package:hacki/config/constants.dart'; import 'package:hacki/config/constants.dart';
@ -32,31 +31,9 @@ class _SearchScreenState extends State<SearchScreen> {
final RefreshController refreshController = RefreshController(); final RefreshController refreshController = RefreshController();
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
final Debouncer debouncer = Debouncer(delay: Durations.oneSecond); final Debouncer debouncer = Debouncer(delay: Durations.oneSecond);
bool showChips = true;
bool shouldOffStageChips = false;
static const Duration chipsAnimationDuration = Durations.ms300; static const Duration chipsAnimationDuration = Durations.ms300;
@override
void initState() {
super.initState();
scrollController.addListener(() {
if (scrollController.position.userScrollDirection ==
ScrollDirection.reverse &&
showChips) {
setState(() {
showChips = false;
});
} else if (scrollController.position.userScrollDirection ==
ScrollDirection.forward &&
!showChips) {
setState(() {
showChips = true;
});
}
});
}
@override @override
void dispose() { void dispose() {
refreshController.dispose(); refreshController.dispose();
@ -108,83 +85,13 @@ class _SearchScreenState extends State<SearchScreen> {
), ),
AnimatedCrossFade( AnimatedCrossFade(
duration: chipsAnimationDuration, duration: chipsAnimationDuration,
crossFadeState: showChips crossFadeState: state.showDateRangeShortcutChips
? CrossFadeState.showSecond ? CrossFadeState.showSecond
: CrossFadeState.showFirst, : CrossFadeState.showFirst,
firstChild: SizedBox.fromSize(), firstChild: SizedBox.fromSize(),
secondChild: Column( secondChild: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
if (state.hasDateFilter &&
state.dateFilter?.startTime != null &&
state.dateFilter?.endTime != null)
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[
const SizedBox(
width: Dimens.pt8,
),
DateTimeShortcutChip.dayBefore(
onDateTimeRangeUpdated: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
startDate: state.dateFilter!.startTime!,
endDate: state.dateFilter!.endTime!,
),
const SizedBox(
width: Dimens.pt8,
),
DateTimeShortcutChip.dayAfter(
onDateTimeRangeUpdated: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
startDate: state.dateFilter!.startTime!,
endDate: state.dateFilter!.endTime!,
),
const SizedBox(
width: Dimens.pt8,
),
DateTimeShortcutChip.weekBefore(
onDateTimeRangeUpdated: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
startDate: state.dateFilter!.startTime!,
endDate: state.dateFilter!.endTime!,
),
const SizedBox(
width: Dimens.pt8,
),
DateTimeShortcutChip.weekAfter(
onDateTimeRangeUpdated: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
startDate: state.dateFilter!.startTime!,
endDate: state.dateFilter!.endTime!,
),
const SizedBox(
width: Dimens.pt8,
),
DateTimeShortcutChip.monthBefore(
onDateTimeRangeUpdated: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
startDate: state.dateFilter!.startTime!,
endDate: state.dateFilter!.endTime!,
),
const SizedBox(
width: Dimens.pt8,
),
DateTimeShortcutChip.monthAfter(
onDateTimeRangeUpdated: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
startDate: state.dateFilter!.startTime!,
endDate: state.dateFilter!.endTime!,
),
],
),
),
SingleChildScrollView( SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(
@ -192,78 +99,143 @@ class _SearchScreenState extends State<SearchScreen> {
const SizedBox( const SizedBox(
width: Dimens.pt8, width: Dimens.pt8,
), ),
DateTimeRangeFilterChip( DateTimeShortcutChip.dayBefore(
filter: state.dateFilter,
initialStartDate: state.dateFilter?.startTime,
initialEndDate: state.dateFilter?.endTime,
onDateTimeRangeUpdated: context onDateTimeRangeUpdated: context
.read<SearchCubit>() .read<SearchCubit>()
.onDateTimeRangeUpdated, .onDateTimeRangeUpdated,
onDateTimeRangeRemoved: context startDate: state.dateFilter?.startTime,
endDate: state.dateFilter?.endTime,
),
const SizedBox(
width: Dimens.pt8,
),
DateTimeShortcutChip.dayAfter(
onDateTimeRangeUpdated: context
.read<SearchCubit>() .read<SearchCubit>()
.removeFilter<DateTimeRangeFilter>, .onDateTimeRangeUpdated,
startDate: state.dateFilter?.startTime,
endDate: state.dateFilter?.endTime,
), ),
const SizedBox( const SizedBox(
width: Dimens.pt8, width: Dimens.pt8,
), ),
PostedByFilterChip( DateTimeShortcutChip.weekBefore(
filter: state.params.get<PostedByFilter>(), onDateTimeRangeUpdated: context
onChanged: context
.read<SearchCubit>() .read<SearchCubit>()
.onPostedByChanged, .onDateTimeRangeUpdated,
startDate: state.dateFilter?.startTime,
endDate: state.dateFilter?.endTime,
), ),
const SizedBox( const SizedBox(
width: Dimens.pt8, width: Dimens.pt8,
), ),
CustomChip( DateTimeShortcutChip.weekAfter(
onSelected: (_) => onDateTimeRangeUpdated: context
context.read<SearchCubit>().onSortToggled(), .read<SearchCubit>()
selected: state.params.sorted, .onDateTimeRangeUpdated,
label: '''newest first''', startDate: state.dateFilter?.startTime,
endDate: state.dateFilter?.endTime,
), ),
const SizedBox( const SizedBox(
width: Dimens.pt8, width: Dimens.pt8,
), ),
for (final CustomDateTimeRange range DateTimeShortcutChip.monthBefore(
in CustomDateTimeRange.values) ...<Widget>[ onDateTimeRangeUpdated: context
CustomRangeFilterChip( .read<SearchCubit>()
range: range, .onDateTimeRangeUpdated,
onTap: context startDate: state.dateFilter?.startTime,
.read<SearchCubit>() endDate: state.dateFilter?.endTime,
.onDateTimeRangeUpdated, ),
), const SizedBox(
const SizedBox( width: Dimens.pt8,
width: Dimens.pt8, ),
), DateTimeShortcutChip.monthAfter(
], onDateTimeRangeUpdated: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
startDate: state.dateFilter?.startTime,
endDate: state.dateFilter?.endTime,
),
], ],
), ),
), ),
SingleChildScrollView( ],
scrollDirection: Axis.horizontal, ),
child: Row( ),
children: <Widget>[ SingleChildScrollView(
for (final TypeTagFilter filter scrollDirection: Axis.horizontal,
in TypeTagFilter.all) ...<Widget>[ child: Row(
const SizedBox( children: <Widget>[
width: Dimens.pt8, const SizedBox(
), width: Dimens.pt8,
CustomChip(
onSelected: (_) => context
.read<SearchCubit>()
.onToggled(filter),
selected: context
.read<SearchCubit>()
.state
.params
.get<TypeTagFilter>() ==
filter,
label: filter.query,
),
],
],
),
), ),
DateTimeRangeFilterChip(
filter: state.dateFilter,
initialStartDate: state.dateFilter?.startTime,
initialEndDate: state.dateFilter?.endTime,
onDateTimeRangeUpdated: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
onDateTimeRangeRemoved: context
.read<SearchCubit>()
.removeFilter<DateTimeRangeFilter>,
),
const SizedBox(
width: Dimens.pt8,
),
PostedByFilterChip(
filter: state.params.get<PostedByFilter>(),
onChanged:
context.read<SearchCubit>().onPostedByChanged,
),
const SizedBox(
width: Dimens.pt8,
),
CustomChip(
onSelected: (_) =>
context.read<SearchCubit>().onSortToggled(),
selected: state.params.sorted,
label: '''newest first''',
),
const SizedBox(
width: Dimens.pt8,
),
for (final CustomDateTimeRange range
in CustomDateTimeRange.values) ...<Widget>[
CustomRangeFilterChip(
range: range,
onTap: context
.read<SearchCubit>()
.onDateTimeRangeUpdated,
),
const SizedBox(
width: Dimens.pt8,
),
],
],
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[
for (final TypeTagFilter filter
in TypeTagFilter.all) ...<Widget>[
const SizedBox(
width: Dimens.pt8,
),
CustomChip(
onSelected: (_) =>
context.read<SearchCubit>().onToggled(filter),
selected: context
.read<SearchCubit>()
.state
.params
.get<TypeTagFilter>() ==
filter,
label: filter.query,
),
],
], ],
), ),
), ),

View File

@ -68,8 +68,8 @@ class DateTimeShortcutChip extends StatelessWidget {
_calculator = ((DateTime date) => date.add(const Duration(days: 30))); _calculator = ((DateTime date) => date.add(const Duration(days: 30)));
final void Function(DateTime, DateTime) onDateTimeRangeUpdated; final void Function(DateTime, DateTime) onDateTimeRangeUpdated;
final DateTime startDate; final DateTime? startDate;
final DateTime endDate; final DateTime? endDate;
final String label; final String label;
final Calculator _calculator; final Calculator _calculator;
@ -77,8 +77,9 @@ class DateTimeShortcutChip extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CustomChip( return CustomChip(
onSelected: (bool value) { onSelected: (bool value) {
final DateTime updatedStartDate = _calculator(startDate); if (startDate == null || endDate == null) return;
final DateTime updatedEndDate = _calculator(endDate); final DateTime updatedStartDate = _calculator(startDate!);
final DateTime updatedEndDate = _calculator(endDate!);
onDateTimeRangeUpdated(updatedStartDate, updatedEndDate); onDateTimeRangeUpdated(updatedStartDate, updatedEndDate);
}, },
selected: false, selected: false,

View File

@ -349,7 +349,8 @@ class CommentTile extends StatelessWidget {
void _collapse(BuildContext context) { void _collapse(BuildContext context) {
HapticFeedbackUtil.selection(); HapticFeedbackUtil.selection();
context.read<CollapseCubit>().collapse(); context.read<CollapseCubit>().collapse();
if (context.read<CollapseCubit>().state.collapsed) { if (context.read<CollapseCubit>().state.collapsed &&
context.read<PreferenceCubit>().state.autoScrollEnabled) {
Future<void>.delayed( Future<void>.delayed(
Durations.ms300, Durations.ms300,
() { () {

View File

@ -237,7 +237,7 @@ class LinkView extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
SizedBox( SizedBox(
height: isUsingSerifFont! ? Dimens.pt2 : Dimens.pt4, height: isUsingSerifFont! ? Dimens.zero : Dimens.pt4,
), ),
Text( Text(
title, title,

View File

@ -1,6 +1,6 @@
name: hacki name: hacki
description: A Hacker News reader. description: A Hacker News reader.
version: 1.7.1+112 version: 1.7.2+113
publish_to: none publish_to: none
environment: environment: