Compare commits

...

2 Commits

Author SHA1 Message Date
e33ff417fb update project.pbxproj (#224) 2023-06-04 21:31:14 -07:00
d8922c2641 prevent over scrolling after collapsing a comment. (#223) 2023-06-04 19:16:01 -07:00
26 changed files with 163 additions and 89 deletions

View File

@ -12,8 +12,7 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart';
abstract class InAppReviewPlatform extends PlatformInterface {
InAppReviewPlatform() : super(token: _token);
static InAppReviewPlatform _instance =
MethodChannelInAppReview() as InAppReviewPlatform;
static InAppReviewPlatform _instance = MethodChannelInAppReview();
static final Object _token = Object();

View File

@ -156,4 +156,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: d28e9a1c7bee335d05ddd795703aad5bf05bb937
COCOAPODS: 1.12.0
COCOAPODS: 1.11.3

View File

@ -78,3 +78,15 @@ abstract class RegExpConstants {
static const String linkSuffix = r'(\)|]|,|\*)(.)*$';
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);
}

View File

@ -5,6 +5,7 @@ import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/config/locator.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/main.dart';
@ -348,6 +349,7 @@ class CommentsCubit extends Cubit<CommentsState> {
init(useCommentCache: true);
}
/// Jump to next root level comment.
void jump(
ItemScrollController itemScrollController,
ItemPositionsListener itemPositionsListener,
@ -378,13 +380,14 @@ class CommentsCubit extends Cubit<CommentsState> {
itemScrollController.scrollTo(
index: i + 1,
alignment: 0.15,
duration: const Duration(milliseconds: 400),
duration: Durations.ms400,
);
return;
}
}
}
/// Jump to previous root level comment.
void jumpUp(
ItemScrollController itemScrollController,
ItemPositionsListener itemPositionsListener,
@ -416,7 +419,7 @@ class CommentsCubit extends Cubit<CommentsState> {
itemScrollController.scrollTo(
index: i + 1,
alignment: 0.15,
duration: const Duration(milliseconds: 400),
duration: Durations.ms400,
);
return;
}

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/config/locator.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart';
@ -11,7 +12,7 @@ part 'edit_state.dart';
class EditCubit extends HydratedCubit<EditState> {
EditCubit({DraftCache? draftCache})
: _draftCache = draftCache ?? locator.get<DraftCache>(),
_debouncer = Debouncer(delay: const Duration(seconds: 1)),
_debouncer = Debouncer(delay: Durations.oneSecond),
super(const EditState.init());
final DraftCache _draftCache;

View File

@ -4,6 +4,7 @@ import 'dart:math';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hacki/blocs/blocs.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/config/locator.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/models/models.dart';
@ -31,7 +32,7 @@ class NotificationCubit extends Cubit<NotificationState> {
if (authState.isLoggedIn && authState.username != _username) {
// Get the user setting.
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.

View File

@ -20,9 +20,6 @@ class PostCubit extends Cubit<PostState> {
text: text,
);
// final successful =
// await Future<bool>.delayed(const Duration(seconds: 2), () => true);
if (successful) {
emit(state.copyWith(status: PostStatus.successful));
} else {

View File

@ -7,6 +7,7 @@ import 'package:equatable/equatable.dart';
import 'package:feature_discovery/feature_discovery.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
@ -268,8 +269,8 @@ class HackiApp extends StatelessWidget {
AsyncSnapshot<AdaptiveThemeMode?> snapshot,
) {
final AdaptiveThemeMode? mode = snapshot.data;
ThemeUtil.updateAndroidStatusBarSetting(
Theme.of(context).brightness,
ThemeUtil.updateStatusBarSetting(
SchedulerBinding.instance.platformDispatcher.platformBrightness,
mode,
);
return BlocBuilder<PreferenceCubit, PreferenceState>(

View File

@ -57,7 +57,7 @@ class _HomeScreenState extends State<HomeScreen>
DeviceScreenType.mobile) {
locator.get<Logger>().i('resetting comments in CommentCache');
Future<void>.delayed(
const Duration(milliseconds: 500),
Durations.ms500,
locator.get<CommentCache>().resetComments,
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart' hide Badge;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/screens/screens.dart';
import 'package:hacki/screens/widgets/widgets.dart';
@ -36,7 +37,7 @@ class TabletHomeScreen extends StatelessWidget {
top: Dimens.zero,
bottom: Dimens.zero,
width: homeScreenWidth,
duration: const Duration(milliseconds: 300),
duration: Durations.ms300,
curve: Curves.elasticOut,
child: homeScreen,
),
@ -52,7 +53,7 @@ class TabletHomeScreen extends StatelessWidget {
top: Dimens.zero,
bottom: Dimens.zero,
left: state.expanded ? Dimens.zero : homeScreenWidth,
duration: const Duration(milliseconds: 300),
duration: Durations.ms300,
curve: Curves.elasticOut,
child: const _TabletStoryView(),
),

View File

@ -153,9 +153,9 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
);
final GlobalKey fontSizeIconButtonKey = GlobalKey();
static const Duration _storyLinkTapThrottleDelay = Duration(seconds: 2);
static const Duration _storyLinkTapThrottleDelay = Durations.twoSeconds;
static const Duration _featureDiscoveryDismissThrottleDelay =
Duration(seconds: 1);
Durations.oneSecond;
@override
void didPop() {

View File

@ -139,6 +139,7 @@ class MainView extends StatelessWidget {
},
onMoreTapped: onMoreTapped,
onRightMoreTapped: onRightMoreTapped,
itemScrollController: itemScrollController,
),
);
},

View File

@ -2,6 +2,7 @@ import 'package:clipboard/clipboard.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/extensions/extensions.dart';
import 'package:hacki/models/item/item.dart';
@ -60,7 +61,7 @@ class _ReplyBoxState extends State<ReplyBox> {
),
child: AnimatedContainer(
height: expanded ? expandedHeight : _collapsedHeight,
duration: const Duration(milliseconds: 200),
duration: Durations.ms200,
decoration: BoxDecoration(
boxShadow: <BoxShadow>[
if (!context.read<SplitViewCubit>().state.enabled)
@ -79,7 +80,7 @@ class _ReplyBoxState extends State<ReplyBox> {
),
AnimatedContainer(
height: expanded ? Dimens.pt36 : Dimens.zero,
duration: const Duration(milliseconds: 200),
duration: Durations.ms200,
),
Row(
children: <Widget>[
@ -107,7 +108,7 @@ class _ReplyBoxState extends State<ReplyBox> {
AnimatedOpacity(
opacity:
expanded ? NumSwitch.on : NumSwitch.off,
duration: const Duration(milliseconds: 300),
duration: Durations.ms300,
child: IconButton(
key: const Key('quote'),
icon: const Icon(

View File

@ -29,7 +29,7 @@ class _ProfileScreenState extends State<ProfileScreen>
final RefreshController refreshControllerFav = RefreshController();
final RefreshController refreshControllerNotification = RefreshController();
final ScrollController scrollController = ScrollController();
final Throttle throttle = Throttle(delay: const Duration(seconds: 2));
final Throttle throttle = Throttle(delay: Durations.twoSeconds);
PageType pageType = PageType.notification;

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:clipboard/clipboard.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
@ -404,8 +405,9 @@ class _SettingsState extends State<Settings> {
AdaptiveTheme.of(context).setSystem();
}
final Brightness brightness = Theme.of(context).brightness;
ThemeUtil.updateAndroidStatusBarSetting(brightness, val);
final Brightness brightness =
SchedulerBinding.instance.platformDispatcher.platformBrightness;
ThemeUtil.updateStatusBarSetting(brightness, val);
}
void showClearCacheDialog() {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_fadein/flutter_fadein.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart';
@ -30,11 +31,11 @@ class SearchScreen extends StatefulWidget {
class _SearchScreenState extends State<SearchScreen> {
final RefreshController refreshController = RefreshController();
final ScrollController scrollController = ScrollController();
final Debouncer debouncer = Debouncer(delay: const Duration(seconds: 1));
final Debouncer debouncer = Debouncer(delay: Durations.oneSecond);
bool showChips = true;
bool shouldOffStageChips = false;
static const Duration chipsAnimationDuration = Duration(milliseconds: 300);
static const Duration chipsAnimationDuration = Durations.ms300;
@override
void initState() {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:hacki/blocs/blocs.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/extensions.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/styles/styles.dart';
import 'package:hacki/utils/utils.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class CommentTile extends StatelessWidget {
const CommentTile({
@ -23,6 +25,7 @@ class CommentTile extends StatelessWidget {
this.actionable = true,
this.level = 0,
this.onTap,
this.itemScrollController,
});
final String? opUsername;
@ -30,6 +33,7 @@ class CommentTile extends StatelessWidget {
final int level;
final bool actionable;
final FetchMode fetchMode;
final ItemScrollController? itemScrollController;
final void Function(Comment)? onReplyTapped;
final void Function(Comment, Rect?)? onMoreTapped;
@ -116,8 +120,7 @@ class CommentTile extends StatelessWidget {
child: InkWell(
onTap: () {
if (actionable) {
HapticFeedbackUtil.selection();
context.read<CollapseCubit>().collapse();
_collapse(context);
} else {
onTap?.call();
}
@ -159,7 +162,7 @@ class CommentTile extends StatelessWidget {
),
),
AnimatedSize(
duration: const Duration(milliseconds: 200),
duration: Durations.ms200,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@ -339,8 +342,26 @@ class CommentTile extends StatelessWidget {
void _onTextTapped(BuildContext context) {
if (context.read<PreferenceCubit>().state.tapAnywhereToCollapseEnabled) {
HapticFeedbackUtil.selection();
context.read<CollapseCubit>().collapse();
_collapse(context);
}
}
void _collapse(BuildContext context) {
HapticFeedbackUtil.selection();
context.read<CollapseCubit>().collapse();
if (context.read<CollapseCubit>().state.collapsed) {
Future<void>.delayed(
Durations.ms300,
() {
itemScrollController?.scrollTo(
index:
context.read<CommentsCubit>().state.comments.indexOf(comment) +
1,
alignment: 0.1,
duration: Durations.ms300,
);
},
);
}
}
}

View File

@ -66,7 +66,7 @@ class _CustomTabBarState extends State<CustomTabBar> {
currentIndex == i ? TextDimens.pt14 : TextDimens.pt10,
color: currentIndex == i ? Palette.orange : Palette.grey,
),
duration: const Duration(milliseconds: 200),
duration: Durations.ms200,
child: Text(
state.tabs.elementAt(i).label,
key: ValueKey<String>(

View File

@ -233,7 +233,7 @@ class _LinkPreviewState extends State<LinkPreview> {
secondChild: loadedWidget,
crossFadeState:
_loading ? CrossFadeState.showFirst : CrossFadeState.showSecond,
duration: const Duration(milliseconds: 500),
duration: Durations.ms500,
);
}
}

View File

@ -15,7 +15,7 @@ class _OnboardingViewState extends State<OnboardingView> {
final PageController pageController = PageController();
final Throttle throttle = Throttle(delay: _throttleDelay);
static const Duration _throttleDelay = Duration(milliseconds: 100);
static const Duration _throttleDelay = Durations.ms100;
static const double _screenshotHeight = 550;
@override
@ -80,7 +80,7 @@ class _OnboardingViewState extends State<OnboardingView> {
} else {
throttle.run(() {
pageController.nextPage(
duration: const Duration(milliseconds: 600),
duration: Durations.ms600,
curve: SpringCurve.underDamped,
);
});

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:hacki/config/constants.dart';
class TapDownWrapper extends StatefulWidget {
const TapDownWrapper({
@ -22,7 +23,7 @@ class _TapDownWrapperState extends State<TapDownWrapper>
@override
void initState() {
controller = AnimationController(
duration: const Duration(milliseconds: 100),
duration: Durations.ms100,
vsync: this,
);

View File

@ -5,6 +5,7 @@ import 'dart:io';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:fast_gbk/fast_gbk.dart';
import 'package:flutter/foundation.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/config/locator.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart';
@ -321,7 +322,7 @@ class WebAnalyzer {
final Uri uri = Uri.parse(url);
final HttpClient ioClient = HttpClient()
..badCertificateCallback = _certificateCheck
..connectionTimeout = const Duration(seconds: 2);
..connectionTimeout = Durations.twoSeconds;
final IOClient client = IOClient(ioClient);
final BaseRequest request = Request('GET', uri)
..followRedirects = true
@ -337,7 +338,7 @@ class WebAnalyzer {
try {
final IOStreamedResponse stream =
await client.send(request).timeout(const Duration(seconds: 10));
await client.send(request).timeout(Durations.tenSeconds);
if (stream.statusCode == HttpStatus.movedTemporarily ||
stream.statusCode == HttpStatus.movedPermanently) {

View File

@ -1,61 +1,92 @@
import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/services.dart';
import 'package:hacki/styles/styles.dart';
abstract class ThemeUtil {
/// Temp fix for the issue:
/// https://github.com/flutter/flutter/issues/119465
static Future<void> updateAndroidStatusBarSetting(
static Future<void> updateStatusBarSetting(
Brightness brightness,
AdaptiveThemeMode? mode,
) async {
if (Platform.isAndroid == false) return;
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
final AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
final int sdk = androidInfo.version.sdkInt;
if (sdk > 28) return;
switch (mode) {
case AdaptiveThemeMode.light:
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
statusBarColor: Palette.transparent,
),
);
case AdaptiveThemeMode.dark:
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.light,
statusBarColor: Palette.transparent,
),
);
case AdaptiveThemeMode.system:
case null:
switch (brightness) {
case Brightness.light:
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
statusBarColor: Palette.transparent,
),
);
case Brightness.dark:
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.light,
statusBarColor: Palette.transparent,
),
);
}
if (Platform.isAndroid) {
switch (mode) {
case AdaptiveThemeMode.light:
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
statusBarColor: Palette.transparent,
),
);
case AdaptiveThemeMode.dark:
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.light,
statusBarColor: Palette.transparent,
),
);
case AdaptiveThemeMode.system:
case null:
switch (brightness) {
case Brightness.light:
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
statusBarColor: Palette.transparent,
),
);
case Brightness.dark:
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.light,
statusBarColor: Palette.transparent,
),
);
}
}
} else {
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,
),
);
}
}
}
}
}

View File

@ -1390,4 +1390,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
flutter: ">=3.10.2"
flutter: ">=3.10.3"

View File

@ -1,11 +1,11 @@
name: hacki
description: A Hacker News reader.
version: 1.7.0+111
version: 1.7.1+112
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: "3.10.2"
flutter: "3.10.3"
dependencies:
adaptive_theme: ^3.2.0