mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
add ability to use material 3. (#286)
This commit is contained in:
4
fastlane/metadata/android/en-US/changelogs/127.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/127.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
- Ability to use Material 3.
|
||||||
|
- Ability to search in thread.
|
||||||
|
- Ability to customize text scale factor.
|
||||||
|
- Ability to customize app's accent color.
|
@ -20,6 +20,8 @@ abstract class Constants {
|
|||||||
'$githubLink/issues/new?title=Found+a+bug+in+Hacki&body=Please+describe+the+problem.';
|
'$githubLink/issues/new?title=Found+a+bug+in+Hacki&body=Please+describe+the+problem.';
|
||||||
static const String wikipediaLink = 'https://en.wikipedia.org/wiki/';
|
static const String wikipediaLink = 'https://en.wikipedia.org/wiki/';
|
||||||
static const String wiktionaryLink = 'https://en.wiktionary.org/wiki/';
|
static const String wiktionaryLink = 'https://en.wiktionary.org/wiki/';
|
||||||
|
static const String hackerNewsItemLinkPrefix =
|
||||||
|
'https://news.ycombinator.com/item?id=';
|
||||||
static const String supportEmail = 'georgefung98@gmail.com';
|
static const String supportEmail = 'georgefung98@gmail.com';
|
||||||
|
|
||||||
static const String _imagePath = 'assets/images';
|
static const String _imagePath = 'assets/images';
|
||||||
|
@ -9,6 +9,7 @@ import 'package:hacki/config/constants.dart';
|
|||||||
import 'package:hacki/config/custom_router.dart';
|
import 'package:hacki/config/custom_router.dart';
|
||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
import 'package:hacki/cubits/cubits.dart';
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
import 'package:hacki/screens/screens.dart';
|
import 'package:hacki/screens/screens.dart';
|
||||||
@ -61,6 +62,10 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
final SembastRepository _sembastRepository;
|
final SembastRepository _sembastRepository;
|
||||||
final Logger _logger;
|
final Logger _logger;
|
||||||
|
|
||||||
|
final ItemScrollController itemScrollController = ItemScrollController();
|
||||||
|
final ItemPositionsListener itemPositionsListener =
|
||||||
|
ItemPositionsListener.create();
|
||||||
|
|
||||||
/// The [StreamSubscription] for stream (both lazy or eager)
|
/// The [StreamSubscription] for stream (both lazy or eager)
|
||||||
/// fetching comments posted directly to the story.
|
/// fetching comments posted directly to the story.
|
||||||
StreamSubscription<Comment>? _streamSubscription;
|
StreamSubscription<Comment>? _streamSubscription;
|
||||||
@ -349,11 +354,20 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
init(useCommentCache: true);
|
init(useCommentCache: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void scrollTo({
|
||||||
|
required int index,
|
||||||
|
double alignment = 0.0,
|
||||||
|
}) {
|
||||||
|
debugPrint('Scrolling to: $index, alignment: $alignment');
|
||||||
|
itemScrollController.scrollTo(
|
||||||
|
index: index,
|
||||||
|
alignment: alignment,
|
||||||
|
duration: Durations.ms400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Scroll to next root level comment.
|
/// Scroll to next root level comment.
|
||||||
void scrollToNextRoot(
|
void scrollToNextRoot() {
|
||||||
ItemScrollController itemScrollController,
|
|
||||||
ItemPositionsListener itemPositionsListener,
|
|
||||||
) {
|
|
||||||
final int totalComments = state.comments.length;
|
final int totalComments = state.comments.length;
|
||||||
final List<Comment> onScreenComments = itemPositionsListener
|
final List<Comment> onScreenComments = itemPositionsListener
|
||||||
.itemPositions.value
|
.itemPositions.value
|
||||||
@ -398,10 +412,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Scroll to previous root level comment.
|
/// Scroll to previous root level comment.
|
||||||
void scrollToPreviousRoot(
|
void scrollToPreviousRoot() {
|
||||||
ItemScrollController itemScrollController,
|
|
||||||
ItemPositionsListener itemPositionsListener,
|
|
||||||
) {
|
|
||||||
final List<Comment> onScreenComments = itemPositionsListener
|
final List<Comment> onScreenComments = itemPositionsListener
|
||||||
.itemPositions.value
|
.itemPositions.value
|
||||||
// The header is also a part of the list view,
|
// The header is also a part of the list view,
|
||||||
@ -436,6 +447,23 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void search(String query) {
|
||||||
|
resetSearch();
|
||||||
|
final String lowercaseQuery = query.toLowerCase();
|
||||||
|
for (final int i in 0.to(state.comments.length, inclusive: false)) {
|
||||||
|
final Comment cmt = state.comments.elementAt(i);
|
||||||
|
if (cmt.text.toLowerCase().contains(lowercaseQuery)) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
matchedComments: <int>[...state.matchedComments, i],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetSearch() => emit(state.copyWith(matchedComments: <int>[]));
|
||||||
|
|
||||||
List<int> _sortKids(List<int> kids) {
|
List<int> _sortKids(List<int> kids) {
|
||||||
switch (state.order) {
|
switch (state.order) {
|
||||||
case CommentsOrder.natural:
|
case CommentsOrder.natural:
|
||||||
|
@ -12,6 +12,7 @@ class CommentsState extends Equatable {
|
|||||||
const CommentsState({
|
const CommentsState({
|
||||||
required this.item,
|
required this.item,
|
||||||
required this.comments,
|
required this.comments,
|
||||||
|
required this.matchedComments,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.fetchParentStatus,
|
required this.fetchParentStatus,
|
||||||
required this.fetchRootStatus,
|
required this.fetchRootStatus,
|
||||||
@ -28,6 +29,7 @@ class CommentsState extends Equatable {
|
|||||||
required this.fetchMode,
|
required this.fetchMode,
|
||||||
required this.order,
|
required this.order,
|
||||||
}) : comments = <Comment>[],
|
}) : comments = <Comment>[],
|
||||||
|
matchedComments = <int>[],
|
||||||
status = CommentsStatus.idle,
|
status = CommentsStatus.idle,
|
||||||
fetchParentStatus = CommentsStatus.idle,
|
fetchParentStatus = CommentsStatus.idle,
|
||||||
fetchRootStatus = CommentsStatus.idle,
|
fetchRootStatus = CommentsStatus.idle,
|
||||||
@ -45,9 +47,13 @@ class CommentsState extends Equatable {
|
|||||||
final bool isOfflineReading;
|
final bool isOfflineReading;
|
||||||
final int currentPage;
|
final int currentPage;
|
||||||
|
|
||||||
|
/// Indexes of comments that matches the query for in-thread search.
|
||||||
|
final List<int> matchedComments;
|
||||||
|
|
||||||
CommentsState copyWith({
|
CommentsState copyWith({
|
||||||
Item? item,
|
Item? item,
|
||||||
List<Comment>? comments,
|
List<Comment>? comments,
|
||||||
|
List<int>? matchedComments,
|
||||||
CommentsStatus? status,
|
CommentsStatus? status,
|
||||||
CommentsStatus? fetchParentStatus,
|
CommentsStatus? fetchParentStatus,
|
||||||
CommentsStatus? fetchRootStatus,
|
CommentsStatus? fetchRootStatus,
|
||||||
@ -60,6 +66,7 @@ class CommentsState extends Equatable {
|
|||||||
return CommentsState(
|
return CommentsState(
|
||||||
item: item ?? this.item,
|
item: item ?? this.item,
|
||||||
comments: comments ?? this.comments,
|
comments: comments ?? this.comments,
|
||||||
|
matchedComments: matchedComments ?? this.matchedComments,
|
||||||
fetchParentStatus: fetchParentStatus ?? this.fetchParentStatus,
|
fetchParentStatus: fetchParentStatus ?? this.fetchParentStatus,
|
||||||
fetchRootStatus: fetchRootStatus ?? this.fetchRootStatus,
|
fetchRootStatus: fetchRootStatus ?? this.fetchRootStatus,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
@ -86,5 +93,6 @@ class CommentsState extends Equatable {
|
|||||||
isOfflineReading,
|
isOfflineReading,
|
||||||
currentPage,
|
currentPage,
|
||||||
comments,
|
comments,
|
||||||
|
matchedComments,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,8 @@ class PreferenceState extends Equatable {
|
|||||||
|
|
||||||
bool get customTabEnabled => _isOn<CustomTabPreference>();
|
bool get customTabEnabled => _isOn<CustomTabPreference>();
|
||||||
|
|
||||||
|
bool get material3Enabled => _isOn<Material3Preference>();
|
||||||
|
|
||||||
double get textScaleFactor =>
|
double get textScaleFactor =>
|
||||||
preferences.singleWhereType<TextScaleFactorPreference>().val;
|
preferences.singleWhereType<TextScaleFactorPreference>().val;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hacki/blocs/auth/auth_bloc.dart';
|
import 'package:hacki/blocs/auth/auth_bloc.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/cubits/cubits.dart';
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
import 'package:hacki/extensions/extensions.dart';
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
@ -103,9 +104,6 @@ mixin ItemActionMixin<T extends StatefulWidget> on State<T> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: ColoredBox(
|
|
||||||
color: Theme.of(context).canvasColor,
|
|
||||||
child: Material(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -115,19 +113,17 @@ mixin ItemActionMixin<T extends StatefulWidget> on State<T> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => context.pop(
|
onTap: () => context.pop(
|
||||||
'https://news.ycombinator.com/item?id=${item.id}',
|
'${Constants.hackerNewsItemLinkPrefix}${item.id}',
|
||||||
),
|
),
|
||||||
title: const Text('Link to HN'),
|
title: const Text('Link to HN'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
linkToShare = 'https://news.ycombinator.com/item?id=${item.id}';
|
linkToShare = '${Constants.hackerNewsItemLinkPrefix}${item.id}';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (linkToShare != null) {
|
if (linkToShare != null) {
|
||||||
|
@ -233,10 +233,13 @@ class HackiApp extends StatelessWidget {
|
|||||||
buildWhen: (PreferenceState previous, PreferenceState current) =>
|
buildWhen: (PreferenceState previous, PreferenceState current) =>
|
||||||
previous.appColor != current.appColor ||
|
previous.appColor != current.appColor ||
|
||||||
previous.font != current.font ||
|
previous.font != current.font ||
|
||||||
previous.textScaleFactor != current.textScaleFactor,
|
previous.textScaleFactor != current.textScaleFactor ||
|
||||||
|
previous.material3Enabled != current.material3Enabled,
|
||||||
builder: (BuildContext context, PreferenceState state) {
|
builder: (BuildContext context, PreferenceState state) {
|
||||||
return AdaptiveTheme(
|
return AdaptiveTheme(
|
||||||
key: ValueKey<String>('${state.appColor}${state.font}'),
|
key: ValueKey<String>(
|
||||||
|
'''${state.appColor}${state.font}${state.material3Enabled}''',
|
||||||
|
),
|
||||||
light: ThemeData(
|
light: ThemeData(
|
||||||
primaryColor: state.appColor,
|
primaryColor: state.appColor,
|
||||||
colorScheme: ColorScheme.fromSwatch(
|
colorScheme: ColorScheme.fromSwatch(
|
||||||
@ -287,7 +290,46 @@ class HackiApp extends StatelessWidget {
|
|||||||
title: 'Hacki',
|
title: 'Hacki',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: (isDarkModeEnabled ? darkTheme : theme).copyWith(
|
theme: (isDarkModeEnabled ? darkTheme : theme).copyWith(
|
||||||
useMaterial3: false,
|
useMaterial3: state.material3Enabled,
|
||||||
|
dividerTheme: state.material3Enabled
|
||||||
|
? DividerThemeData(
|
||||||
|
color: Palette.grey.withOpacity(0.2),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
switchTheme: state.material3Enabled
|
||||||
|
? SwitchThemeData(
|
||||||
|
trackColor: MaterialStateProperty.resolveWith(
|
||||||
|
(Set<MaterialState> states) {
|
||||||
|
if (states
|
||||||
|
.contains(MaterialState.selected)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return Palette.grey.withOpacity(0.2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
bottomSheetTheme: state.material3Enabled
|
||||||
|
? BottomSheetThemeData(
|
||||||
|
modalElevation: 0,
|
||||||
|
backgroundColor: isDarkModeEnabled
|
||||||
|
? Palette.black
|
||||||
|
: Palette.white,
|
||||||
|
shape: const RoundedRectangleBorder(),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
inputDecorationTheme: state.material3Enabled
|
||||||
|
? InputDecorationTheme(
|
||||||
|
enabledBorder: UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: isDarkModeEnabled
|
||||||
|
? Palette.white
|
||||||
|
: Palette.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
routerConfig: router,
|
routerConfig: router,
|
||||||
),
|
),
|
||||||
|
@ -25,13 +25,18 @@ enum DiscoverableFeature {
|
|||||||
featureId: 'jump_up_button_with_long_press',
|
featureId: 'jump_up_button_with_long_press',
|
||||||
title: 'Shortcut',
|
title: 'Shortcut',
|
||||||
description:
|
description:
|
||||||
'''Tapping on this button will take you to the previous off-screen root level comment.\n\nLong press on it to jump to the very beginning of this thread.''',
|
'''Tapping on this button will take you to the previous root level comment.\n\nLong press on it to jump to the very beginning of this thread.''',
|
||||||
),
|
),
|
||||||
jumpDownButton(
|
jumpDownButton(
|
||||||
featureId: 'jump_down_button_with_long_press',
|
featureId: 'jump_down_button_with_long_press',
|
||||||
title: 'Shortcut',
|
title: 'Shortcut',
|
||||||
description:
|
description:
|
||||||
'''Tapping on this button will take you to the next off-screen root level comment.\n\nLong press on it to jump to the end of this thread.''',
|
'''Tapping on this button will take you to the next root level comment.\n\nLong press on it to jump to the end of this thread.''',
|
||||||
|
),
|
||||||
|
searchInThread(
|
||||||
|
featureId: 'search_in_thread',
|
||||||
|
title: 'Search in Thread',
|
||||||
|
description: '''Search for comments in this thread.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
const DiscoverableFeature({
|
const DiscoverableFeature({
|
||||||
|
@ -43,6 +43,7 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
|||||||
const ReaderModePreference(),
|
const ReaderModePreference(),
|
||||||
const CustomTabPreference(),
|
const CustomTabPreference(),
|
||||||
const EyeCandyModePreference(),
|
const EyeCandyModePreference(),
|
||||||
|
const Material3Preference(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ const bool _storyUrlModeDefaultValue = true;
|
|||||||
const bool _collapseModeDefaultValue = true;
|
const bool _collapseModeDefaultValue = true;
|
||||||
const bool _autoScrollModeDefaultValue = false;
|
const bool _autoScrollModeDefaultValue = false;
|
||||||
const bool _customTabModeDefaultValue = false;
|
const bool _customTabModeDefaultValue = false;
|
||||||
|
const bool _material3ModeDefaultValue = false;
|
||||||
const double _textScaleFactorDefaultValue = 1;
|
const double _textScaleFactorDefaultValue = 1;
|
||||||
final int _fetchModeDefaultValue = FetchMode.eager.index;
|
final int _fetchModeDefaultValue = FetchMode.eager.index;
|
||||||
final int _commentsOrderDefaultValue = CommentsOrder.natural.index;
|
final int _commentsOrderDefaultValue = CommentsOrder.natural.index;
|
||||||
@ -285,6 +287,26 @@ class EyeCandyModePreference extends BooleanPreference {
|
|||||||
String get subtitle => 'some sort of magic.';
|
String get subtitle => 'some sort of magic.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Material3Preference extends BooleanPreference {
|
||||||
|
const Material3Preference({bool? val})
|
||||||
|
: super(val: val ?? _material3ModeDefaultValue);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Material3Preference copyWith({required bool? val}) {
|
||||||
|
return Material3Preference(val: val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get key => 'material3Mode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'Enable Material 3';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get subtitle =>
|
||||||
|
'''experiment feature. Please open an issue on GitHub if you notice anything weird.''';
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether or not to use Custom Tabs for launching URLs.
|
/// Whether or not to use Custom Tabs for launching URLs.
|
||||||
/// If false, default browser will be used.
|
/// If false, default browser will be used.
|
||||||
///
|
///
|
||||||
|
@ -49,7 +49,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
super.didPopNext();
|
super.didPopNext();
|
||||||
if (context.read<StoriesBloc>().deviceScreenType ==
|
if (context.read<StoriesBloc>().deviceScreenType ==
|
||||||
DeviceScreenType.mobile) {
|
DeviceScreenType.mobile) {
|
||||||
locator.get<Logger>().i('resetting comments in CommentCache');
|
locator.get<Logger>().i('Resetting comments in CommentCache');
|
||||||
Future<void>.delayed(
|
Future<void>.delayed(
|
||||||
Durations.ms500,
|
Durations.ms500,
|
||||||
locator.get<CommentCache>().resetComments,
|
locator.get<CommentCache>().resetComments,
|
||||||
|
@ -142,9 +142,6 @@ class _ItemScreenState extends State<ItemScreen>
|
|||||||
final TextEditingController commentEditingController =
|
final TextEditingController commentEditingController =
|
||||||
TextEditingController();
|
TextEditingController();
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
final ItemScrollController itemScrollController = ItemScrollController();
|
|
||||||
final ItemPositionsListener itemPositionsListener =
|
|
||||||
ItemPositionsListener.create();
|
|
||||||
final ScrollOffsetListener scrollOffsetListener =
|
final ScrollOffsetListener scrollOffsetListener =
|
||||||
ScrollOffsetListener.create();
|
ScrollOffsetListener.create();
|
||||||
final Throttle storyLinkTapThrottle = Throttle(
|
final Throttle storyLinkTapThrottle = Throttle(
|
||||||
@ -187,6 +184,7 @@ class _ItemScreenState extends State<ItemScreen>
|
|||||||
DiscoverableFeature.openStoryInWebView.featureId,
|
DiscoverableFeature.openStoryInWebView.featureId,
|
||||||
DiscoverableFeature.jumpUpButton.featureId,
|
DiscoverableFeature.jumpUpButton.featureId,
|
||||||
DiscoverableFeature.jumpDownButton.featureId,
|
DiscoverableFeature.jumpDownButton.featureId,
|
||||||
|
DiscoverableFeature.searchInThread.featureId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -272,8 +270,6 @@ class _ItemScreenState extends State<ItemScreen>
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: MainView(
|
child: MainView(
|
||||||
itemScrollController: itemScrollController,
|
|
||||||
itemPositionsListener: itemPositionsListener,
|
|
||||||
scrollOffsetListener: scrollOffsetListener,
|
scrollOffsetListener: scrollOffsetListener,
|
||||||
commentEditingController: commentEditingController,
|
commentEditingController: commentEditingController,
|
||||||
authState: authState,
|
authState: authState,
|
||||||
@ -313,13 +309,10 @@ class _ItemScreenState extends State<ItemScreen>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Positioned(
|
const Positioned(
|
||||||
right: Dimens.pt12,
|
right: Dimens.pt12,
|
||||||
bottom: Dimens.pt36,
|
bottom: Dimens.pt36,
|
||||||
child: CustomFloatingActionButton(
|
child: CustomFloatingActionButton(),
|
||||||
itemScrollController: itemScrollController,
|
|
||||||
itemPositionsListener: itemPositionsListener,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: Dimens.zero,
|
bottom: Dimens.zero,
|
||||||
@ -348,8 +341,6 @@ class _ItemScreenState extends State<ItemScreen>
|
|||||||
fontSizeIconButtonKey: fontSizeIconButtonKey,
|
fontSizeIconButtonKey: fontSizeIconButtonKey,
|
||||||
),
|
),
|
||||||
body: MainView(
|
body: MainView(
|
||||||
itemScrollController: itemScrollController,
|
|
||||||
itemPositionsListener: itemPositionsListener,
|
|
||||||
scrollOffsetListener: scrollOffsetListener,
|
scrollOffsetListener: scrollOffsetListener,
|
||||||
commentEditingController: commentEditingController,
|
commentEditingController: commentEditingController,
|
||||||
authState: authState,
|
authState: authState,
|
||||||
@ -358,10 +349,7 @@ class _ItemScreenState extends State<ItemScreen>
|
|||||||
onMoreTapped: onMoreTapped,
|
onMoreTapped: onMoreTapped,
|
||||||
onRightMoreTapped: onRightMoreTapped,
|
onRightMoreTapped: onRightMoreTapped,
|
||||||
),
|
),
|
||||||
floatingActionButton: CustomFloatingActionButton(
|
floatingActionButton: const CustomFloatingActionButton(),
|
||||||
itemScrollController: itemScrollController,
|
|
||||||
itemPositionsListener: itemPositionsListener,
|
|
||||||
),
|
|
||||||
bottomSheet: ReplyBox(
|
bottomSheet: ReplyBox(
|
||||||
textEditingController: commentEditingController,
|
textEditingController: commentEditingController,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
@ -437,10 +425,6 @@ class _ItemScreenState extends State<ItemScreen>
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: ColoredBox(
|
|
||||||
color: Theme.of(context).canvasColor,
|
|
||||||
child: Material(
|
|
||||||
color: Palette.transparent,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -472,8 +456,6 @@ class _ItemScreenState extends State<ItemScreen>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -34,6 +34,7 @@ class CustomAppBar extends AppBar {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
],
|
],
|
||||||
|
const InThreadSearchIconButton(),
|
||||||
IconButton(
|
IconButton(
|
||||||
key: fontSizeIconButtonKey,
|
key: fontSizeIconButtonKey,
|
||||||
icon: Text(
|
icon: Text(
|
||||||
|
@ -7,18 +7,12 @@ import 'package:hacki/models/discoverable_feature.dart';
|
|||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
import 'package:hacki/utils/utils.dart';
|
import 'package:hacki/utils/utils.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
|
||||||
|
|
||||||
class CustomFloatingActionButton extends StatelessWidget {
|
class CustomFloatingActionButton extends StatelessWidget {
|
||||||
const CustomFloatingActionButton({
|
const CustomFloatingActionButton({
|
||||||
required this.itemScrollController,
|
|
||||||
required this.itemPositionsListener,
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ItemScrollController itemScrollController;
|
|
||||||
final ItemPositionsListener itemPositionsListener;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<CommentsCubit, CommentsState>(
|
return BlocBuilder<CommentsCubit, CommentsState>(
|
||||||
@ -45,10 +39,8 @@ class CustomFloatingActionButton extends StatelessWidget {
|
|||||||
color: Palette.white,
|
color: Palette.white,
|
||||||
),
|
),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onLongPress: () => itemScrollController.scrollTo(
|
onLongPress: () =>
|
||||||
index: 0,
|
context.read<CommentsCubit>().scrollTo(index: 0),
|
||||||
duration: Durations.ms400,
|
|
||||||
),
|
|
||||||
child: FloatingActionButton.small(
|
child: FloatingActionButton.small(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
Theme.of(context).scaffoldBackgroundColor,
|
||||||
@ -58,10 +50,7 @@ class CustomFloatingActionButton extends StatelessWidget {
|
|||||||
heroTag: UniqueKey().hashCode,
|
heroTag: UniqueKey().hashCode,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
HapticFeedbackUtil.selection();
|
HapticFeedbackUtil.selection();
|
||||||
context.read<CommentsCubit>().scrollToPreviousRoot(
|
context.read<CommentsCubit>().scrollToPreviousRoot();
|
||||||
itemScrollController,
|
|
||||||
itemPositionsListener,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.keyboard_arrow_up,
|
Icons.keyboard_arrow_up,
|
||||||
@ -77,10 +66,9 @@ class CustomFloatingActionButton extends StatelessWidget {
|
|||||||
color: Palette.white,
|
color: Palette.white,
|
||||||
),
|
),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onLongPress: () => itemScrollController.scrollTo(
|
onLongPress: () => context
|
||||||
index: state.comments.length,
|
.read<CommentsCubit>()
|
||||||
duration: Durations.ms400,
|
.scrollTo(index: state.comments.length),
|
||||||
),
|
|
||||||
child: FloatingActionButton.small(
|
child: FloatingActionButton.small(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
Theme.of(context).scaffoldBackgroundColor,
|
||||||
@ -89,10 +77,7 @@ class CustomFloatingActionButton extends StatelessWidget {
|
|||||||
heroTag: UniqueKey().hashCode,
|
heroTag: UniqueKey().hashCode,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
HapticFeedbackUtil.selection();
|
HapticFeedbackUtil.selection();
|
||||||
context.read<CommentsCubit>().scrollToNextRoot(
|
context.read<CommentsCubit>().scrollToNextRoot();
|
||||||
itemScrollController,
|
|
||||||
itemPositionsListener,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.keyboard_arrow_down,
|
Icons.keyboard_arrow_down,
|
||||||
|
106
lib/screens/item/widgets/in_thread_search_icon_button.dart
Normal file
106
lib/screens/item/widgets/in_thread_search_icon_button.dart
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:hacki/cubits/comments/comments_cubit.dart';
|
||||||
|
import 'package:hacki/models/models.dart';
|
||||||
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
|
||||||
|
class InThreadSearchIconButton extends StatelessWidget {
|
||||||
|
const InThreadSearchIconButton({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider<CommentsCubit>.value(
|
||||||
|
value: context.read<CommentsCubit>(),
|
||||||
|
child: IconButton(
|
||||||
|
tooltip: 'Search in thread',
|
||||||
|
icon: const CustomDescribedFeatureOverlay(
|
||||||
|
tapTarget: Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Palette.white,
|
||||||
|
),
|
||||||
|
feature: DiscoverableFeature.searchInThread,
|
||||||
|
child: Icon(
|
||||||
|
Icons.search,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
showDragHandle: true,
|
||||||
|
backgroundColor: Theme.of(context).canvasColor,
|
||||||
|
builder: (BuildContext _) {
|
||||||
|
return BlocProvider<CommentsCubit>.value(
|
||||||
|
value: context.read<CommentsCubit>(),
|
||||||
|
child: BlocBuilder<CommentsCubit, CommentsState>(
|
||||||
|
buildWhen: (CommentsState previous, CommentsState current) =>
|
||||||
|
previous.matchedComments != current.matchedComments,
|
||||||
|
builder: (BuildContext context, CommentsState state) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context).size.height - Dimens.pt120,
|
||||||
|
color: Theme.of(context).canvasColor,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: Dimens.pt8,
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
cursorColor: Theme.of(context).primaryColor,
|
||||||
|
autocorrect: false,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Search in this thread',
|
||||||
|
suffixText: state.matchedComments.isEmpty
|
||||||
|
? ''
|
||||||
|
: '${state.matchedComments.length} results',
|
||||||
|
focusedBorder: UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: context.read<CommentsCubit>().search,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
if (state.matchedComments.isEmpty)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(top: Dimens.pt120),
|
||||||
|
child: CenteredText.empty(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
for (final int i in state.matchedComments)
|
||||||
|
CommentTile(
|
||||||
|
comment: state.comments.elementAt(i),
|
||||||
|
fetchMode: FetchMode.lazy,
|
||||||
|
actionable: false,
|
||||||
|
onTap: () {
|
||||||
|
context.pop();
|
||||||
|
context.read<CommentsCubit>().scrollTo(
|
||||||
|
index: i + 1,
|
||||||
|
alignment: 0.1,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/models/discoverable_feature.dart';
|
import 'package:hacki/models/discoverable_feature.dart';
|
||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
@ -27,7 +28,7 @@ class LinkIconButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () => LinkUtil.launch(
|
onPressed: () => LinkUtil.launch(
|
||||||
'https://news.ycombinator.com/item?id=$storyId',
|
'${Constants.hackerNewsItemLinkPrefix}$storyId',
|
||||||
context,
|
context,
|
||||||
useHackiForHnLink: false,
|
useHackiForHnLink: false,
|
||||||
),
|
),
|
||||||
|
@ -18,8 +18,6 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
|||||||
|
|
||||||
class MainView extends StatelessWidget {
|
class MainView extends StatelessWidget {
|
||||||
const MainView({
|
const MainView({
|
||||||
required this.itemScrollController,
|
|
||||||
required this.itemPositionsListener,
|
|
||||||
required this.scrollOffsetListener,
|
required this.scrollOffsetListener,
|
||||||
required this.commentEditingController,
|
required this.commentEditingController,
|
||||||
required this.authState,
|
required this.authState,
|
||||||
@ -30,8 +28,6 @@ class MainView extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ItemScrollController itemScrollController;
|
|
||||||
final ItemPositionsListener itemPositionsListener;
|
|
||||||
final ScrollOffsetListener scrollOffsetListener;
|
final ScrollOffsetListener scrollOffsetListener;
|
||||||
final TextEditingController commentEditingController;
|
final TextEditingController commentEditingController;
|
||||||
final AuthState authState;
|
final AuthState authState;
|
||||||
@ -67,8 +63,10 @@ class MainView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: ScrollablePositionedList.builder(
|
child: ScrollablePositionedList.builder(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
itemScrollController: itemScrollController,
|
itemScrollController:
|
||||||
itemPositionsListener: itemPositionsListener,
|
context.read<CommentsCubit>().itemScrollController,
|
||||||
|
itemPositionsListener:
|
||||||
|
context.read<CommentsCubit>().itemPositionsListener,
|
||||||
itemCount: state.comments.length + 2,
|
itemCount: state.comments.length + 2,
|
||||||
padding: EdgeInsets.only(top: topPadding),
|
padding: EdgeInsets.only(top: topPadding),
|
||||||
scrollOffsetListener: scrollOffsetListener,
|
scrollOffsetListener: scrollOffsetListener,
|
||||||
@ -130,7 +128,6 @@ class MainView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
onMoreTapped: onMoreTapped,
|
onMoreTapped: onMoreTapped,
|
||||||
onRightMoreTapped: onRightMoreTapped,
|
onRightMoreTapped: onRightMoreTapped,
|
||||||
itemScrollController: itemScrollController,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -185,7 +182,7 @@ class _ParentItemSection extends StatelessWidget {
|
|||||||
final ValueChanged<Comment> onRightMoreTapped;
|
final ValueChanged<Comment> onRightMoreTapped;
|
||||||
|
|
||||||
static const double _viewParentButtonWidth = 100;
|
static const double _viewParentButtonWidth = 100;
|
||||||
static const double _viewRootButtonWidth = 80;
|
static const double _viewRootButtonWidth = 85;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -66,11 +66,7 @@ class MorePopupMenu extends StatelessWidget {
|
|||||||
builder: (BuildContext context, VoteState voteState) {
|
builder: (BuildContext context, VoteState voteState) {
|
||||||
final bool upvoted = voteState.vote == Vote.up;
|
final bool upvoted = voteState.vote == Vote.up;
|
||||||
final bool downvoted = voteState.vote == Vote.down;
|
final bool downvoted = voteState.vote == Vote.down;
|
||||||
return ColoredBox(
|
return Column(
|
||||||
color: Theme.of(context).canvasColor,
|
|
||||||
child: Material(
|
|
||||||
color: Palette.transparent,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
BlocProvider<UserCubit>(
|
BlocProvider<UserCubit>(
|
||||||
@ -116,8 +112,7 @@ class MorePopupMenu extends StatelessWidget {
|
|||||||
state.user.about,
|
state.user.about,
|
||||||
),
|
),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color:
|
color: Theme.of(context).primaryColor,
|
||||||
Theme.of(context).primaryColor,
|
|
||||||
),
|
),
|
||||||
onOpen: (LinkableElement link) =>
|
onOpen: (LinkableElement link) =>
|
||||||
LinkUtil.launch(
|
LinkUtil.launch(
|
||||||
@ -170,8 +165,7 @@ class MorePopupMenu extends StatelessWidget {
|
|||||||
? TextStyle(color: Theme.of(context).primaryColor)
|
? TextStyle(color: Theme.of(context).primaryColor)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
subtitle:
|
subtitle: item is Story ? Text(item.score.toString()) : null,
|
||||||
item is Story ? Text(item.score.toString()) : null,
|
|
||||||
onTap: context.read<VoteCubit>().upvote,
|
onTap: context.read<VoteCubit>().upvote,
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -233,8 +227,6 @@ class MorePopupMenu extends StatelessWidget {
|
|||||||
onTap: () => context.pop(MenuAction.cancel),
|
onTap: () => context.pop(MenuAction.cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -245,6 +237,8 @@ class MorePopupMenu extends StatelessWidget {
|
|||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Theme.of(context).canvasColor,
|
||||||
|
showDragHandle: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return BlocProvider<SearchCubit>(
|
return BlocProvider<SearchCubit>(
|
||||||
create: (_) => SearchCubit()
|
create: (_) => SearchCubit()
|
||||||
@ -256,19 +250,10 @@ class MorePopupMenu extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
height: MediaQuery.of(context).size.height - Dimens.pt120,
|
height: MediaQuery.of(context).size.height - Dimens.pt120,
|
||||||
color: Theme.of(context).canvasColor,
|
color: Theme.of(context).canvasColor,
|
||||||
margin: const EdgeInsets.only(top: Dimens.pt12),
|
child: const Material(
|
||||||
child: Material(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Expanded(
|
||||||
height: Dimens.pt4,
|
|
||||||
width: Dimens.pt24,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Palette.grey,
|
|
||||||
borderRadius: BorderRadius.circular(Dimens.pt16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Expanded(
|
|
||||||
child: SearchScreen(
|
child: SearchScreen(
|
||||||
fromUserDialog: true,
|
fromUserDialog: true,
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export 'custom_app_bar.dart';
|
export 'custom_app_bar.dart';
|
||||||
export 'custom_floating_action_button.dart';
|
export 'custom_floating_action_button.dart';
|
||||||
export 'fav_icon_button.dart';
|
export 'fav_icon_button.dart';
|
||||||
|
export 'in_thread_search_icon_button.dart';
|
||||||
export 'link_icon_button.dart';
|
export 'link_icon_button.dart';
|
||||||
export 'login_dialog.dart';
|
export 'login_dialog.dart';
|
||||||
export 'main_view.dart';
|
export 'main_view.dart';
|
||||||
|
@ -32,6 +32,12 @@ class CenteredText extends StatelessWidget {
|
|||||||
text: 'blocked',
|
text: 'blocked',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const CenteredText.empty({Key? key})
|
||||||
|
: this(
|
||||||
|
key: key,
|
||||||
|
text: 'empty',
|
||||||
|
);
|
||||||
|
|
||||||
final String text;
|
final String text;
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import 'package:hacki/screens/widgets/widgets.dart';
|
|||||||
import 'package:hacki/services/services.dart';
|
import 'package:hacki/services/services.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
import 'package:hacki/utils/utils.dart';
|
import 'package:hacki/utils/utils.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
|
||||||
|
|
||||||
class CommentTile extends StatelessWidget {
|
class CommentTile extends StatelessWidget {
|
||||||
const CommentTile({
|
const CommentTile({
|
||||||
@ -27,7 +26,6 @@ class CommentTile extends StatelessWidget {
|
|||||||
this.selectable = true,
|
this.selectable = true,
|
||||||
this.level = 0,
|
this.level = 0,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.itemScrollController,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? opUsername;
|
final String? opUsername;
|
||||||
@ -37,7 +35,6 @@ class CommentTile extends StatelessWidget {
|
|||||||
final bool collapsable;
|
final bool collapsable;
|
||||||
final bool selectable;
|
final bool selectable;
|
||||||
final FetchMode fetchMode;
|
final FetchMode fetchMode;
|
||||||
final ItemScrollController? itemScrollController;
|
|
||||||
|
|
||||||
final void Function(Comment)? onReplyTapped;
|
final void Function(Comment)? onReplyTapped;
|
||||||
final void Function(Comment, Rect?)? onMoreTapped;
|
final void Function(Comment, Rect?)? onMoreTapped;
|
||||||
@ -370,14 +367,14 @@ class CommentTile extends StatelessWidget {
|
|||||||
..collapse(onStateChanged: HapticFeedbackUtil.selection);
|
..collapse(onStateChanged: HapticFeedbackUtil.selection);
|
||||||
if (collapseCubit.state.collapsed &&
|
if (collapseCubit.state.collapsed &&
|
||||||
preferenceCubit.state.autoScrollEnabled) {
|
preferenceCubit.state.autoScrollEnabled) {
|
||||||
final List<Comment> comments =
|
final CommentsCubit commentsCubit = context.read<CommentsCubit>();
|
||||||
context.read<CommentsCubit>().state.comments;
|
final List<Comment> comments = commentsCubit.state.comments;
|
||||||
final int indexOfNextComment = comments.indexOf(comment) + 1;
|
final int indexOfNextComment = comments.indexOf(comment) + 1;
|
||||||
if (indexOfNextComment < comments.length) {
|
if (indexOfNextComment < comments.length) {
|
||||||
Future<void>.delayed(
|
Future<void>.delayed(
|
||||||
Durations.ms300,
|
Durations.ms300,
|
||||||
() {
|
() {
|
||||||
itemScrollController?.scrollTo(
|
commentsCubit.itemScrollController.scrollTo(
|
||||||
index: indexOfNextComment,
|
index: indexOfNextComment,
|
||||||
alignment: 0.1,
|
alignment: 0.1,
|
||||||
duration: Durations.ms300,
|
duration: Durations.ms300,
|
||||||
|
@ -15,11 +15,17 @@ class CustomChip extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final bool useMaterial3 = Theme.of(context).useMaterial3;
|
||||||
return FilterChip(
|
return FilterChip(
|
||||||
shadowColor: Palette.transparent,
|
shadowColor: Palette.transparent,
|
||||||
selectedShadowColor: Palette.transparent,
|
selectedShadowColor: Palette.transparent,
|
||||||
backgroundColor: Palette.transparent,
|
backgroundColor: Palette.transparent,
|
||||||
shape: StadiumBorder(
|
side: useMaterial3 && !selected
|
||||||
|
? BorderSide(color: Theme.of(context).colorScheme.onSurface)
|
||||||
|
: null,
|
||||||
|
shape: Theme.of(context).useMaterial3
|
||||||
|
? null
|
||||||
|
: StadiumBorder(
|
||||||
side: BorderSide(color: Theme.of(context).primaryColor),
|
side: BorderSide(color: Theme.of(context).primaryColor),
|
||||||
),
|
),
|
||||||
label: Text(label),
|
label: Text(label),
|
||||||
|
@ -37,6 +37,7 @@ class _TapDownWrapperState extends State<TapDownWrapper>
|
|||||||
onTapDown: onTapDown,
|
onTapDown: onTapDown,
|
||||||
onTapUp: onTapUp,
|
onTapUp: onTapUp,
|
||||||
onTapCancel: onTapCancel,
|
onTapCancel: onTapCancel,
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
child: AnimatedBuilder(
|
child: AnimatedBuilder(
|
||||||
animation:
|
animation:
|
||||||
CurvedAnimation(parent: controller, curve: Curves.decelerate),
|
CurvedAnimation(parent: controller, curve: Curves.decelerate),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 2.0.1+126
|
version: 2.1.0+127
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
Reference in New Issue
Block a user