Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
92dac6b932 | |||
20365393a3 | |||
8d238744c7 | |||
e33ff417fb | |||
d8922c2641 | |||
c6e0461857 | |||
30ca356dc8 |
@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@mipmap/ic_launcher_adaptive_back"/>
|
<background android:drawable="@mipmap/ic_launcher_adaptive_back"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_adaptive_fore"/>
|
<foreground android:drawable="@mipmap/ic_launcher_adaptive_fore"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 940 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
@ -24,6 +24,6 @@ subprojects {
|
|||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
tasks.register("clean", Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
BIN
assets/hacki-github.xcf
Normal file
BIN
assets/hacki.xcf
Normal file
BIN
assets/screenshots/hacki-1.png
Normal file
After Width: | Height: | Size: 890 KiB |
BIN
assets/screenshots/hacki-2.png
Normal file
After Width: | Height: | Size: 873 KiB |
BIN
assets/screenshots/hacki-3.png
Normal file
After Width: | Height: | Size: 770 KiB |
BIN
assets/screenshots/hacki-4.png
Normal file
After Width: | Height: | Size: 517 KiB |
@ -78,3 +78,15 @@ abstract class RegExpConstants {
|
|||||||
static const String linkSuffix = r'(\)|]|,|\*)(.)*$';
|
static const String linkSuffix = r'(\)|]|,|\*)(.)*$';
|
||||||
static const String number = '[0-9]+';
|
static const String number = '[0-9]+';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class Durations {
|
||||||
|
static const Duration ms100 = Duration(milliseconds: 100);
|
||||||
|
static const Duration ms200 = Duration(milliseconds: 200);
|
||||||
|
static const Duration ms300 = Duration(milliseconds: 300);
|
||||||
|
static const Duration ms400 = Duration(milliseconds: 400);
|
||||||
|
static const Duration ms500 = Duration(milliseconds: 500);
|
||||||
|
static const Duration ms600 = Duration(milliseconds: 600);
|
||||||
|
static const Duration oneSecond = Duration(seconds: 1);
|
||||||
|
static const Duration twoSeconds = Duration(seconds: 2);
|
||||||
|
static const Duration tenSeconds = Duration(seconds: 10);
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ import 'package:bloc/bloc.dart';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hacki/config/constants.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/main.dart';
|
import 'package:hacki/main.dart';
|
||||||
@ -348,6 +349,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
init(useCommentCache: true);
|
init(useCommentCache: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Jump to next root level comment.
|
||||||
void jump(
|
void jump(
|
||||||
ItemScrollController itemScrollController,
|
ItemScrollController itemScrollController,
|
||||||
ItemPositionsListener itemPositionsListener,
|
ItemPositionsListener itemPositionsListener,
|
||||||
@ -378,13 +380,14 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
itemScrollController.scrollTo(
|
itemScrollController.scrollTo(
|
||||||
index: i + 1,
|
index: i + 1,
|
||||||
alignment: 0.15,
|
alignment: 0.15,
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: Durations.ms400,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Jump to previous root level comment.
|
||||||
void jumpUp(
|
void jumpUp(
|
||||||
ItemScrollController itemScrollController,
|
ItemScrollController itemScrollController,
|
||||||
ItemPositionsListener itemPositionsListener,
|
ItemPositionsListener itemPositionsListener,
|
||||||
@ -416,7 +419,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
itemScrollController.scrollTo(
|
itemScrollController.scrollTo(
|
||||||
index: i + 1,
|
index: i + 1,
|
||||||
alignment: 0.15,
|
alignment: 0.15,
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: Durations.ms400,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.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';
|
||||||
@ -11,7 +12,7 @@ part 'edit_state.dart';
|
|||||||
class EditCubit extends HydratedCubit<EditState> {
|
class EditCubit extends HydratedCubit<EditState> {
|
||||||
EditCubit({DraftCache? draftCache})
|
EditCubit({DraftCache? draftCache})
|
||||||
: _draftCache = draftCache ?? locator.get<DraftCache>(),
|
: _draftCache = draftCache ?? locator.get<DraftCache>(),
|
||||||
_debouncer = Debouncer(delay: const Duration(seconds: 1)),
|
_debouncer = Debouncer(delay: Durations.oneSecond),
|
||||||
super(const EditState.init());
|
super(const EditState.init());
|
||||||
|
|
||||||
final DraftCache _draftCache;
|
final DraftCache _draftCache;
|
||||||
|
@ -4,6 +4,7 @@ import 'dart:math';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:hacki/blocs/blocs.dart';
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
|
import 'package:hacki/config/constants.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/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
@ -31,7 +32,7 @@ class NotificationCubit extends Cubit<NotificationState> {
|
|||||||
if (authState.isLoggedIn && authState.username != _username) {
|
if (authState.isLoggedIn && authState.username != _username) {
|
||||||
// Get the user setting.
|
// Get the user setting.
|
||||||
if (_preferenceCubit.state.notificationEnabled) {
|
if (_preferenceCubit.state.notificationEnabled) {
|
||||||
Future<void>.delayed(const Duration(seconds: 2), init);
|
Future<void>.delayed(Durations.twoSeconds, init);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for setting changes in the future.
|
// Listen for setting changes in the future.
|
||||||
|
@ -20,9 +20,6 @@ class PostCubit extends Cubit<PostState> {
|
|||||||
text: text,
|
text: text,
|
||||||
);
|
);
|
||||||
|
|
||||||
// final successful =
|
|
||||||
// await Future<bool>.delayed(const Duration(seconds: 2), () => true);
|
|
||||||
|
|
||||||
if (successful) {
|
if (successful) {
|
||||||
emit(state.copyWith(status: PostStatus.successful));
|
emit(state.copyWith(status: PostStatus.successful));
|
||||||
} else {
|
} else {
|
||||||
|
@ -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();
|
||||||
|
@ -23,6 +23,12 @@ class SearchState extends Equatable {
|
|||||||
final SearchStatus status;
|
final SearchStatus status;
|
||||||
final SearchParams params;
|
final SearchParams params;
|
||||||
|
|
||||||
|
bool get hasDateFilter =>
|
||||||
|
params.filters.whereType<DateTimeRangeFilter>().isNotEmpty;
|
||||||
|
|
||||||
|
DateTimeRangeFilter? get dateFilter =>
|
||||||
|
params.filters.whereType<DateTimeRangeFilter>().singleOrNull;
|
||||||
|
|
||||||
SearchState copyWith({
|
SearchState copyWith({
|
||||||
List<Item>? results,
|
List<Item>? results,
|
||||||
SearchStatus? status,
|
SearchStatus? status,
|
||||||
@ -42,3 +48,11 @@ class SearchState extends Equatable {
|
|||||||
params,
|
params,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SearchStateExtension on SearchState {
|
||||||
|
bool get showDateRangeShortcutChips {
|
||||||
|
return hasDateFilter &&
|
||||||
|
dateFilter?.startTime != null &&
|
||||||
|
dateFilter?.endTime != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import 'package:equatable/equatable.dart';
|
|||||||
import 'package:feature_discovery/feature_discovery.dart';
|
import 'package:feature_discovery/feature_discovery.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
@ -268,8 +269,8 @@ class HackiApp extends StatelessWidget {
|
|||||||
AsyncSnapshot<AdaptiveThemeMode?> snapshot,
|
AsyncSnapshot<AdaptiveThemeMode?> snapshot,
|
||||||
) {
|
) {
|
||||||
final AdaptiveThemeMode? mode = snapshot.data;
|
final AdaptiveThemeMode? mode = snapshot.data;
|
||||||
ThemeUtil.updateAndroidStatusBarSetting(
|
ThemeUtil.updateStatusBarSetting(
|
||||||
Theme.of(context).brightness,
|
SchedulerBinding.instance.platformDispatcher.platformBrightness,
|
||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
return BlocBuilder<PreferenceCubit, PreferenceState>(
|
return BlocBuilder<PreferenceCubit, PreferenceState>(
|
||||||
|
@ -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 {
|
||||||
|
@ -30,14 +30,27 @@ class DateTimeRangeFilter implements NumericFilter {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get query {
|
String get query {
|
||||||
|
if (startTime == null || endTime == null) return '';
|
||||||
|
|
||||||
final int? startTimestamp = startTime == null
|
final int? startTimestamp = startTime == null
|
||||||
? null
|
? null
|
||||||
: startTime!.toUtc().millisecondsSinceEpoch ~/ 1000;
|
: startTime!.toUtc().millisecondsSinceEpoch ~/ 1000;
|
||||||
final int? endTimestamp = endTime == null
|
int? endTimestamp = endTime == null
|
||||||
? null
|
? null
|
||||||
: endTime!.toUtc().millisecondsSinceEpoch ~/ 1000;
|
: endTime!.toUtc().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
|
||||||
|
if (startTimestamp == endTimestamp) {
|
||||||
|
endTimestamp = startTime!
|
||||||
|
.add(const Duration(hours: 24))
|
||||||
|
.toUtc()
|
||||||
|
.millisecondsSinceEpoch ~/
|
||||||
|
1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTimestamp == null || endTimestamp == null) return '';
|
||||||
|
|
||||||
final String query =
|
final String query =
|
||||||
'''${startTimestamp == null ? '' : 'created_at_i>$startTimestamp'},${endTimestamp == null ? '' : 'created_at_i<$endTimestamp'}''';
|
'''created_at_i>=$startTimestamp, created_at_i<=$endTimestamp''';
|
||||||
|
|
||||||
if (query.endsWith(',')) {
|
if (query.endsWith(',')) {
|
||||||
return query.replaceFirst(',', '');
|
return query.replaceFirst(',', '');
|
||||||
|
@ -57,7 +57,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
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(
|
||||||
const Duration(milliseconds: 500),
|
Durations.ms500,
|
||||||
locator.get<CommentCache>().resetComments,
|
locator.get<CommentCache>().resetComments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart' hide Badge;
|
import 'package:flutter/material.dart' hide Badge;
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/cubits/cubits.dart';
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
import 'package:hacki/screens/screens.dart';
|
import 'package:hacki/screens/screens.dart';
|
||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
@ -36,7 +37,7 @@ class TabletHomeScreen extends StatelessWidget {
|
|||||||
top: Dimens.zero,
|
top: Dimens.zero,
|
||||||
bottom: Dimens.zero,
|
bottom: Dimens.zero,
|
||||||
width: homeScreenWidth,
|
width: homeScreenWidth,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: Durations.ms300,
|
||||||
curve: Curves.elasticOut,
|
curve: Curves.elasticOut,
|
||||||
child: homeScreen,
|
child: homeScreen,
|
||||||
),
|
),
|
||||||
@ -52,7 +53,7 @@ class TabletHomeScreen extends StatelessWidget {
|
|||||||
top: Dimens.zero,
|
top: Dimens.zero,
|
||||||
bottom: Dimens.zero,
|
bottom: Dimens.zero,
|
||||||
left: state.expanded ? Dimens.zero : homeScreenWidth,
|
left: state.expanded ? Dimens.zero : homeScreenWidth,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: Durations.ms300,
|
||||||
curve: Curves.elasticOut,
|
curve: Curves.elasticOut,
|
||||||
child: const _TabletStoryView(),
|
child: const _TabletStoryView(),
|
||||||
),
|
),
|
||||||
|
@ -153,9 +153,9 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
|
|||||||
);
|
);
|
||||||
final GlobalKey fontSizeIconButtonKey = GlobalKey();
|
final GlobalKey fontSizeIconButtonKey = GlobalKey();
|
||||||
|
|
||||||
static const Duration _storyLinkTapThrottleDelay = Duration(seconds: 2);
|
static const Duration _storyLinkTapThrottleDelay = Durations.twoSeconds;
|
||||||
static const Duration _featureDiscoveryDismissThrottleDelay =
|
static const Duration _featureDiscoveryDismissThrottleDelay =
|
||||||
Duration(seconds: 1);
|
Durations.oneSecond;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didPop() {
|
void didPop() {
|
||||||
|
@ -139,6 +139,7 @@ class MainView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
onMoreTapped: onMoreTapped,
|
onMoreTapped: onMoreTapped,
|
||||||
onRightMoreTapped: onRightMoreTapped,
|
onRightMoreTapped: onRightMoreTapped,
|
||||||
|
itemScrollController: itemScrollController,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@ import 'package:clipboard/clipboard.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
import 'package:flutter_feather_icons/flutter_feather_icons.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/item/item.dart';
|
import 'package:hacki/models/item/item.dart';
|
||||||
@ -60,7 +61,7 @@ class _ReplyBoxState extends State<ReplyBox> {
|
|||||||
),
|
),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
height: expanded ? expandedHeight : _collapsedHeight,
|
height: expanded ? expandedHeight : _collapsedHeight,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: Durations.ms200,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
boxShadow: <BoxShadow>[
|
boxShadow: <BoxShadow>[
|
||||||
if (!context.read<SplitViewCubit>().state.enabled)
|
if (!context.read<SplitViewCubit>().state.enabled)
|
||||||
@ -79,7 +80,7 @@ class _ReplyBoxState extends State<ReplyBox> {
|
|||||||
),
|
),
|
||||||
AnimatedContainer(
|
AnimatedContainer(
|
||||||
height: expanded ? Dimens.pt36 : Dimens.zero,
|
height: expanded ? Dimens.pt36 : Dimens.zero,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: Durations.ms200,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -107,7 +108,7 @@ class _ReplyBoxState extends State<ReplyBox> {
|
|||||||
AnimatedOpacity(
|
AnimatedOpacity(
|
||||||
opacity:
|
opacity:
|
||||||
expanded ? NumSwitch.on : NumSwitch.off,
|
expanded ? NumSwitch.on : NumSwitch.off,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: Durations.ms300,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
key: const Key('quote'),
|
key: const Key('quote'),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
|
@ -29,7 +29,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
final RefreshController refreshControllerFav = RefreshController();
|
final RefreshController refreshControllerFav = RefreshController();
|
||||||
final RefreshController refreshControllerNotification = RefreshController();
|
final RefreshController refreshControllerNotification = RefreshController();
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
final Throttle throttle = Throttle(delay: const Duration(seconds: 2));
|
final Throttle throttle = Throttle(delay: Durations.twoSeconds);
|
||||||
|
|
||||||
PageType pageType = PageType.notification;
|
PageType pageType = PageType.notification;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||||
import 'package:clipboard/clipboard.dart';
|
import 'package:clipboard/clipboard.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
||||||
@ -404,8 +405,9 @@ class _SettingsState extends State<Settings> {
|
|||||||
AdaptiveTheme.of(context).setSystem();
|
AdaptiveTheme.of(context).setSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Brightness brightness = Theme.of(context).brightness;
|
final Brightness brightness =
|
||||||
ThemeUtil.updateAndroidStatusBarSetting(brightness, val);
|
SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||||
|
ThemeUtil.updateStatusBarSetting(brightness, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showClearCacheDialog() {
|
void showClearCacheDialog() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/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';
|
||||||
@ -28,7 +29,17 @@ class SearchScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _SearchScreenState extends State<SearchScreen> {
|
class _SearchScreenState extends State<SearchScreen> {
|
||||||
final RefreshController refreshController = RefreshController();
|
final RefreshController refreshController = RefreshController();
|
||||||
final Debouncer debouncer = Debouncer(delay: const Duration(seconds: 1));
|
final ScrollController scrollController = ScrollController();
|
||||||
|
final Debouncer debouncer = Debouncer(delay: Durations.oneSecond);
|
||||||
|
|
||||||
|
static const Duration chipsAnimationDuration = Durations.ms300;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
refreshController.dispose();
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -72,6 +83,85 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: Dimens.pt6,
|
height: Dimens.pt6,
|
||||||
),
|
),
|
||||||
|
AnimatedCrossFade(
|
||||||
|
duration: chipsAnimationDuration,
|
||||||
|
crossFadeState: state.showDateRangeShortcutChips
|
||||||
|
? CrossFadeState.showSecond
|
||||||
|
: CrossFadeState.showFirst,
|
||||||
|
firstChild: SizedBox.fromSize(),
|
||||||
|
secondChild: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
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(
|
||||||
@ -80,7 +170,9 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||||||
width: Dimens.pt8,
|
width: Dimens.pt8,
|
||||||
),
|
),
|
||||||
DateTimeRangeFilterChip(
|
DateTimeRangeFilterChip(
|
||||||
filter: state.params.get<DateTimeRangeFilter>(),
|
filter: state.dateFilter,
|
||||||
|
initialStartDate: state.dateFilter?.startTime,
|
||||||
|
initialEndDate: state.dateFilter?.endTime,
|
||||||
onDateTimeRangeUpdated: context
|
onDateTimeRangeUpdated: context
|
||||||
.read<SearchCubit>()
|
.read<SearchCubit>()
|
||||||
.onDateTimeRangeUpdated,
|
.onDateTimeRangeUpdated,
|
||||||
@ -200,11 +292,15 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
controller: refreshController,
|
controller: refreshController,
|
||||||
|
scrollController: scrollController,
|
||||||
onRefresh: () {},
|
onRefresh: () {},
|
||||||
onLoading: () {
|
onLoading: () {
|
||||||
context.read<SearchCubit>().loadMore();
|
context.read<SearchCubit>().loadMore();
|
||||||
},
|
},
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
physics: state.results.isEmpty
|
||||||
|
? const NeverScrollableScrollPhysics()
|
||||||
|
: null,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
...state.results
|
...state.results
|
||||||
.map(
|
.map(
|
||||||
|
@ -6,12 +6,16 @@ import 'package:intl/intl.dart';
|
|||||||
class DateTimeRangeFilterChip extends StatelessWidget {
|
class DateTimeRangeFilterChip extends StatelessWidget {
|
||||||
const DateTimeRangeFilterChip({
|
const DateTimeRangeFilterChip({
|
||||||
required this.filter,
|
required this.filter,
|
||||||
|
required this.initialStartDate,
|
||||||
|
required this.initialEndDate,
|
||||||
required this.onDateTimeRangeUpdated,
|
required this.onDateTimeRangeUpdated,
|
||||||
required this.onDateTimeRangeRemoved,
|
required this.onDateTimeRangeRemoved,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final DateTimeRangeFilter? filter;
|
final DateTimeRangeFilter? filter;
|
||||||
|
final DateTime? initialStartDate;
|
||||||
|
final DateTime? initialEndDate;
|
||||||
final void Function(DateTime, DateTime) onDateTimeRangeUpdated;
|
final void Function(DateTime, DateTime) onDateTimeRangeUpdated;
|
||||||
final VoidCallback onDateTimeRangeRemoved;
|
final VoidCallback onDateTimeRangeRemoved;
|
||||||
|
|
||||||
@ -25,6 +29,9 @@ class DateTimeRangeFilterChip extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
firstDate: DateTime.now().subtract(const Duration(days: 20 * 365)),
|
firstDate: DateTime.now().subtract(const Duration(days: 20 * 365)),
|
||||||
lastDate: DateTime.now(),
|
lastDate: DateTime.now(),
|
||||||
|
initialDateRange: initialStartDate != null && initialEndDate != null
|
||||||
|
? DateTimeRange(start: initialStartDate!, end: initialEndDate!)
|
||||||
|
: null,
|
||||||
).then((DateTimeRange? range) {
|
).then((DateTimeRange? range) {
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
onDateTimeRangeUpdated(range.start, range.end);
|
onDateTimeRangeUpdated(range.start, range.end);
|
||||||
@ -34,11 +41,22 @@ class DateTimeRangeFilterChip extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
selected: filter != null,
|
selected: filter != null,
|
||||||
label:
|
label: _label,
|
||||||
'''from ${_formatDateTime(filter?.startTime) ?? 'X'} to ${_formatDateTime(filter?.endTime) ?? 'Y'}''',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get _label {
|
||||||
|
final DateTime? start = filter?.startTime;
|
||||||
|
final DateTime? end = filter?.endTime;
|
||||||
|
if (start == null && end == null) {
|
||||||
|
return '''from X to Y''';
|
||||||
|
} else if (start == end) {
|
||||||
|
return '''from ${_formatDateTime(start)}''';
|
||||||
|
} else {
|
||||||
|
return '''from ${_formatDateTime(start)} to ${_formatDateTime(end)}''';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static String? _formatDateTime(DateTime? dateTime) {
|
static String? _formatDateTime(DateTime? dateTime) {
|
||||||
if (dateTime == null) return null;
|
if (dateTime == null) return null;
|
||||||
|
|
||||||
|
89
lib/screens/search/widgets/date_time_shortcut_chip.dart
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hacki/screens/search/widgets/date_time_range_filter_chip.dart';
|
||||||
|
import 'package:hacki/screens/widgets/widgets.dart' show CustomChip;
|
||||||
|
|
||||||
|
typedef Calculator = DateTime Function(DateTime);
|
||||||
|
|
||||||
|
/// A set of chips that perform addition or subtraction on the date selected
|
||||||
|
/// by [DateTimeRangeFilterChip]
|
||||||
|
class DateTimeShortcutChip extends StatelessWidget {
|
||||||
|
const DateTimeShortcutChip({
|
||||||
|
required this.onDateTimeRangeUpdated,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
required this.label,
|
||||||
|
required Calculator calculator,
|
||||||
|
super.key,
|
||||||
|
}) : _calculator = calculator;
|
||||||
|
|
||||||
|
DateTimeShortcutChip.dayBefore({
|
||||||
|
required this.onDateTimeRangeUpdated,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
super.key,
|
||||||
|
}) : label = '- day',
|
||||||
|
_calculator =
|
||||||
|
((DateTime date) => date.subtract(const Duration(hours: 24)));
|
||||||
|
|
||||||
|
DateTimeShortcutChip.dayAfter({
|
||||||
|
required this.onDateTimeRangeUpdated,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
super.key,
|
||||||
|
}) : label = '+ day',
|
||||||
|
_calculator = ((DateTime date) => date.add(const Duration(hours: 24)));
|
||||||
|
|
||||||
|
DateTimeShortcutChip.weekBefore({
|
||||||
|
required this.onDateTimeRangeUpdated,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
super.key,
|
||||||
|
}) : label = '- week',
|
||||||
|
_calculator =
|
||||||
|
((DateTime date) => date.subtract(const Duration(days: 7)));
|
||||||
|
|
||||||
|
DateTimeShortcutChip.weekAfter({
|
||||||
|
required this.onDateTimeRangeUpdated,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
super.key,
|
||||||
|
}) : label = '+ week',
|
||||||
|
_calculator = ((DateTime date) => date.add(const Duration(days: 7)));
|
||||||
|
|
||||||
|
DateTimeShortcutChip.monthBefore({
|
||||||
|
required this.onDateTimeRangeUpdated,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
super.key,
|
||||||
|
}) : label = '- 30 days',
|
||||||
|
_calculator =
|
||||||
|
((DateTime date) => date.subtract(const Duration(days: 30)));
|
||||||
|
|
||||||
|
DateTimeShortcutChip.monthAfter({
|
||||||
|
required this.onDateTimeRangeUpdated,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
super.key,
|
||||||
|
}) : label = '+ 30 days',
|
||||||
|
_calculator = ((DateTime date) => date.add(const Duration(days: 30)));
|
||||||
|
|
||||||
|
final void Function(DateTime, DateTime) onDateTimeRangeUpdated;
|
||||||
|
final DateTime? startDate;
|
||||||
|
final DateTime? endDate;
|
||||||
|
final String label;
|
||||||
|
final Calculator _calculator;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CustomChip(
|
||||||
|
onSelected: (bool value) {
|
||||||
|
if (startDate == null || endDate == null) return;
|
||||||
|
final DateTime updatedStartDate = _calculator(startDate!);
|
||||||
|
final DateTime updatedEndDate = _calculator(endDate!);
|
||||||
|
onDateTimeRangeUpdated(updatedStartDate, updatedEndDate);
|
||||||
|
},
|
||||||
|
selected: false,
|
||||||
|
label: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
export 'custom_range_filter_chip.dart';
|
export 'custom_range_filter_chip.dart';
|
||||||
export 'date_time_range_filter_chip.dart';
|
export 'date_time_range_filter_chip.dart';
|
||||||
|
export 'date_time_shortcut_chip.dart';
|
||||||
export 'posted_by_filter_chip.dart';
|
export 'posted_by_filter_chip.dart';
|
||||||
|
@ -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:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:hacki/blocs/blocs.dart';
|
import 'package:hacki/blocs/blocs.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';
|
||||||
@ -9,6 +10,7 @@ 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({
|
||||||
@ -23,6 +25,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
this.actionable = true,
|
this.actionable = true,
|
||||||
this.level = 0,
|
this.level = 0,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.itemScrollController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? opUsername;
|
final String? opUsername;
|
||||||
@ -30,6 +33,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
final int level;
|
final int level;
|
||||||
final bool actionable;
|
final bool actionable;
|
||||||
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;
|
||||||
@ -116,8 +120,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (actionable) {
|
if (actionable) {
|
||||||
HapticFeedbackUtil.selection();
|
_collapse(context);
|
||||||
context.read<CollapseCubit>().collapse();
|
|
||||||
} else {
|
} else {
|
||||||
onTap?.call();
|
onTap?.call();
|
||||||
}
|
}
|
||||||
@ -159,7 +162,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedSize(
|
AnimatedSize(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: Durations.ms200,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -339,8 +342,27 @@ class CommentTile extends StatelessWidget {
|
|||||||
|
|
||||||
void _onTextTapped(BuildContext context) {
|
void _onTextTapped(BuildContext context) {
|
||||||
if (context.read<PreferenceCubit>().state.tapAnywhereToCollapseEnabled) {
|
if (context.read<PreferenceCubit>().state.tapAnywhereToCollapseEnabled) {
|
||||||
HapticFeedbackUtil.selection();
|
_collapse(context);
|
||||||
context.read<CollapseCubit>().collapse();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _collapse(BuildContext context) {
|
||||||
|
HapticFeedbackUtil.selection();
|
||||||
|
context.read<CollapseCubit>().collapse();
|
||||||
|
if (context.read<CollapseCubit>().state.collapsed &&
|
||||||
|
context.read<PreferenceCubit>().state.autoScrollEnabled) {
|
||||||
|
Future<void>.delayed(
|
||||||
|
Durations.ms300,
|
||||||
|
() {
|
||||||
|
itemScrollController?.scrollTo(
|
||||||
|
index:
|
||||||
|
context.read<CommentsCubit>().state.comments.indexOf(comment) +
|
||||||
|
1,
|
||||||
|
alignment: 0.1,
|
||||||
|
duration: Durations.ms300,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ class _CustomTabBarState extends State<CustomTabBar> {
|
|||||||
currentIndex == i ? TextDimens.pt14 : TextDimens.pt10,
|
currentIndex == i ? TextDimens.pt14 : TextDimens.pt10,
|
||||||
color: currentIndex == i ? Palette.orange : Palette.grey,
|
color: currentIndex == i ? Palette.orange : Palette.grey,
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: Durations.ms200,
|
||||||
child: Text(
|
child: Text(
|
||||||
state.tabs.elementAt(i).label,
|
state.tabs.elementAt(i).label,
|
||||||
key: ValueKey<String>(
|
key: ValueKey<String>(
|
||||||
|
@ -17,7 +17,7 @@ class DeviceGestureWrapper extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MediaQuery(
|
return MediaQuery(
|
||||||
data: const MediaQueryData(
|
data: const MediaQueryData(
|
||||||
gestureSettings: DeviceGestureSettings(touchSlop: 7.9),
|
gestureSettings: DeviceGestureSettings(touchSlop: 12),
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
@ -233,7 +233,7 @@ class _LinkPreviewState extends State<LinkPreview> {
|
|||||||
secondChild: loadedWidget,
|
secondChild: loadedWidget,
|
||||||
crossFadeState:
|
crossFadeState:
|
||||||
_loading ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
_loading ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||||
duration: const Duration(milliseconds: 500),
|
duration: Durations.ms500,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -15,7 +15,7 @@ class _OnboardingViewState extends State<OnboardingView> {
|
|||||||
final PageController pageController = PageController();
|
final PageController pageController = PageController();
|
||||||
final Throttle throttle = Throttle(delay: _throttleDelay);
|
final Throttle throttle = Throttle(delay: _throttleDelay);
|
||||||
|
|
||||||
static const Duration _throttleDelay = Duration(milliseconds: 100);
|
static const Duration _throttleDelay = Durations.ms100;
|
||||||
static const double _screenshotHeight = 550;
|
static const double _screenshotHeight = 550;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -80,7 +80,7 @@ class _OnboardingViewState extends State<OnboardingView> {
|
|||||||
} else {
|
} else {
|
||||||
throttle.run(() {
|
throttle.run(() {
|
||||||
pageController.nextPage(
|
pageController.nextPage(
|
||||||
duration: const Duration(milliseconds: 600),
|
duration: Durations.ms600,
|
||||||
curve: SpringCurve.underDamped,
|
curve: SpringCurve.underDamped,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
|
|
||||||
class TapDownWrapper extends StatefulWidget {
|
class TapDownWrapper extends StatefulWidget {
|
||||||
const TapDownWrapper({
|
const TapDownWrapper({
|
||||||
@ -22,7 +23,7 @@ class _TapDownWrapperState extends State<TapDownWrapper>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
controller = AnimationController(
|
controller = AnimationController(
|
||||||
duration: const Duration(milliseconds: 100),
|
duration: Durations.ms100,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import 'dart:io';
|
|||||||
import 'package:collection/collection.dart' show IterableExtension;
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:fast_gbk/fast_gbk.dart';
|
import 'package:fast_gbk/fast_gbk.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.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';
|
||||||
@ -321,7 +322,7 @@ class WebAnalyzer {
|
|||||||
final Uri uri = Uri.parse(url);
|
final Uri uri = Uri.parse(url);
|
||||||
final HttpClient ioClient = HttpClient()
|
final HttpClient ioClient = HttpClient()
|
||||||
..badCertificateCallback = _certificateCheck
|
..badCertificateCallback = _certificateCheck
|
||||||
..connectionTimeout = const Duration(seconds: 2);
|
..connectionTimeout = Durations.twoSeconds;
|
||||||
final IOClient client = IOClient(ioClient);
|
final IOClient client = IOClient(ioClient);
|
||||||
final BaseRequest request = Request('GET', uri)
|
final BaseRequest request = Request('GET', uri)
|
||||||
..followRedirects = true
|
..followRedirects = true
|
||||||
@ -337,7 +338,7 @@ class WebAnalyzer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final IOStreamedResponse stream =
|
final IOStreamedResponse stream =
|
||||||
await client.send(request).timeout(const Duration(seconds: 10));
|
await client.send(request).timeout(Durations.tenSeconds);
|
||||||
|
|
||||||
if (stream.statusCode == HttpStatus.movedTemporarily ||
|
if (stream.statusCode == HttpStatus.movedTemporarily ||
|
||||||
stream.statusCode == HttpStatus.movedPermanently) {
|
stream.statusCode == HttpStatus.movedPermanently) {
|
||||||
|
@ -1,61 +1,92 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
|
||||||
abstract class ThemeUtil {
|
abstract class ThemeUtil {
|
||||||
/// Temp fix for the issue:
|
static Future<void> updateStatusBarSetting(
|
||||||
/// https://github.com/flutter/flutter/issues/119465
|
|
||||||
static Future<void> updateAndroidStatusBarSetting(
|
|
||||||
Brightness brightness,
|
Brightness brightness,
|
||||||
AdaptiveThemeMode? mode,
|
AdaptiveThemeMode? mode,
|
||||||
) async {
|
) async {
|
||||||
if (Platform.isAndroid == false) return;
|
if (Platform.isAndroid) {
|
||||||
|
switch (mode) {
|
||||||
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
case AdaptiveThemeMode.light:
|
||||||
final AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
final int sdk = androidInfo.version.sdkInt;
|
const SystemUiOverlayStyle(
|
||||||
|
statusBarBrightness: Brightness.dark,
|
||||||
if (sdk > 28) return;
|
statusBarIconBrightness: Brightness.dark,
|
||||||
switch (mode) {
|
statusBarColor: Palette.transparent,
|
||||||
case AdaptiveThemeMode.light:
|
),
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
);
|
||||||
const SystemUiOverlayStyle(
|
case AdaptiveThemeMode.dark:
|
||||||
statusBarBrightness: Brightness.dark,
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
statusBarIconBrightness: Brightness.dark,
|
const SystemUiOverlayStyle(
|
||||||
statusBarColor: Palette.transparent,
|
statusBarBrightness: Brightness.light,
|
||||||
),
|
statusBarIconBrightness: Brightness.light,
|
||||||
);
|
statusBarColor: Palette.transparent,
|
||||||
case AdaptiveThemeMode.dark:
|
),
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
);
|
||||||
const SystemUiOverlayStyle(
|
case AdaptiveThemeMode.system:
|
||||||
statusBarBrightness: Brightness.light,
|
case null:
|
||||||
statusBarIconBrightness: Brightness.light,
|
switch (brightness) {
|
||||||
statusBarColor: Palette.transparent,
|
case Brightness.light:
|
||||||
),
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
);
|
const SystemUiOverlayStyle(
|
||||||
case AdaptiveThemeMode.system:
|
statusBarBrightness: Brightness.dark,
|
||||||
case null:
|
statusBarIconBrightness: Brightness.dark,
|
||||||
switch (brightness) {
|
statusBarColor: Palette.transparent,
|
||||||
case Brightness.light:
|
),
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
);
|
||||||
const SystemUiOverlayStyle(
|
case Brightness.dark:
|
||||||
statusBarBrightness: Brightness.dark,
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
statusBarIconBrightness: Brightness.dark,
|
const SystemUiOverlayStyle(
|
||||||
statusBarColor: Palette.transparent,
|
statusBarBrightness: Brightness.light,
|
||||||
),
|
statusBarIconBrightness: Brightness.light,
|
||||||
);
|
statusBarColor: Palette.transparent,
|
||||||
case Brightness.dark:
|
),
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
);
|
||||||
const SystemUiOverlayStyle(
|
}
|
||||||
statusBarBrightness: Brightness.light,
|
}
|
||||||
statusBarIconBrightness: Brightness.light,
|
} else {
|
||||||
statusBarColor: Palette.transparent,
|
switch (mode) {
|
||||||
),
|
case AdaptiveThemeMode.light:
|
||||||
);
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
}
|
const SystemUiOverlayStyle(
|
||||||
|
statusBarBrightness: Brightness.light,
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
statusBarColor: Palette.transparent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case AdaptiveThemeMode.dark:
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
|
const SystemUiOverlayStyle(
|
||||||
|
statusBarBrightness: Brightness.dark,
|
||||||
|
statusBarIconBrightness: Brightness.dark,
|
||||||
|
statusBarColor: Palette.transparent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case AdaptiveThemeMode.system:
|
||||||
|
case null:
|
||||||
|
switch (brightness) {
|
||||||
|
case Brightness.light:
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
|
const SystemUiOverlayStyle(
|
||||||
|
statusBarBrightness: Brightness.light,
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
statusBarColor: Palette.transparent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case Brightness.dark:
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
|
const SystemUiOverlayStyle(
|
||||||
|
statusBarBrightness: Brightness.dark,
|
||||||
|
statusBarIconBrightness: Brightness.dark,
|
||||||
|
statusBarColor: Palette.transparent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1390,4 +1390,4 @@ packages:
|
|||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.0 <4.0.0"
|
dart: ">=3.0.0 <4.0.0"
|
||||||
flutter: ">=3.10.1"
|
flutter: ">=3.10.3"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 1.6.0+110
|
version: 1.7.4+115
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
flutter: "3.10.1"
|
flutter: "3.10.3"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
adaptive_theme: ^3.2.0
|
adaptive_theme: ^3.2.0
|
||||||
|