Compare commits

...

7 Commits

15 changed files with 230 additions and 66 deletions

View File

@ -63,18 +63,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.4" version: "10.0.5"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.5"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
@ -95,18 +95,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.11.1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.15.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -164,10 +164,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.0" version: "0.7.2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -180,10 +180,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.1" version: "14.2.5"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.18.0-18.0.pre.54"

View File

@ -0,0 +1 @@
- Improved tablet mode, you can now resize submission panel.

View File

@ -60,7 +60,7 @@ void main() {
expect(firstStoryFinder, findsOneWidget); expect(firstStoryFinder, findsOneWidget);
await tester.tap(firstStoryFinder); await tester.tap(firstStoryFinder);
await tester.pump(const Duration(seconds: 4)); await tester.pump(const Duration(seconds: 5));
}, },
reportKey: 'scrolling_timeline', reportKey: 'scrolling_timeline',
); );

View File

@ -46,7 +46,7 @@
<string>mailto</string> <string>mailto</string>
</array> </array>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>15.0</string> <string>14.0</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>

View File

@ -126,7 +126,9 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
.copyWithStatusUpdated(type: type, to: Status.inProgress), .copyWithStatusUpdated(type: type, to: Status.inProgress),
); );
_offlineRepository _offlineRepository
.getCachedStoriesStream(ids: ids.sublist(0, _pageSize)) .getCachedStoriesStream(
ids: ids.sublist(0, min(_pageSize, ids.length)),
)
.listen((Story story) => add(StoryLoaded(story: story, type: type))) .listen((Story story) => add(StoryLoaded(story: story, type: type)))
.onDone(() => add(StoryLoadingCompleted(type: type))); .onDone(() => add(StoryLoadingCompleted(type: type)));
} else if (event.useApi || state.dataSource == HackerNewsDataSource.api) { } else if (event.useApi || state.dataSource == HackerNewsDataSource.api) {
@ -142,7 +144,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
await _hackerNewsRepository await _hackerNewsRepository
.fetchStoriesStream( .fetchStoriesStream(
ids: ids.sublist(0, _pageSize), ids: ids.sublist(0, min(_pageSize, ids.length)),
sequential: true, sequential: true,
) )
.listen((Story story) { .listen((Story story) {

View File

@ -1,13 +1,14 @@
import 'package:bloc/bloc.dart';
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/screens/screens.dart'; import 'package:hacki/screens/screens.dart';
import 'package:hacki/services/services.dart'; import 'package:hacki/services/services.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
part 'split_view_state.dart'; part 'split_view_state.dart';
class SplitViewCubit extends Cubit<SplitViewState> with Loggable { class SplitViewCubit extends HydratedCubit<SplitViewState> with Loggable {
SplitViewCubit({ SplitViewCubit({
CommentCache? commentCache, CommentCache? commentCache,
}) : _commentCache = commentCache ?? locator.get<CommentCache>(), }) : _commentCache = commentCache ?? locator.get<CommentCache>(),
@ -25,8 +26,36 @@ class SplitViewCubit extends Cubit<SplitViewState> with Loggable {
void disableSplitView() => emit(state.copyWith(enabled: false)); void disableSplitView() => emit(state.copyWith(enabled: false));
void zoom() => emit(state.copyWith(expanded: !state.expanded)); void zoom() => emit(
state.copyWith(
expanded: !state.expanded,
resizingAnimationDuration: AppDurations.ms300,
),
);
void updateSubmissionPanelWidth(double width) => emit(
state.copyWith(
submissionPanelWidth: width,
resizingAnimationDuration: Duration.zero,
),
);
@override @override
String get logIdentifier => '[SplitViewCubit]'; String get logIdentifier => '[SplitViewCubit]';
static const String _submissionPanelWidthKey = 'submissionPanelWidth';
@override
SplitViewState? fromJson(Map<String, dynamic> json) {
return state.copyWith(
submissionPanelWidth: json[_submissionPanelWidthKey] as double?,
);
}
@override
Map<String, dynamic>? toJson(SplitViewState state) {
return <String, dynamic>{
_submissionPanelWidthKey: state.submissionPanelWidth,
};
}
} }

View File

@ -5,25 +5,36 @@ class SplitViewState extends Equatable {
required this.itemScreenArgs, required this.itemScreenArgs,
required this.expanded, required this.expanded,
required this.enabled, required this.enabled,
required this.resizingAnimationDuration,
this.submissionPanelWidth,
}); });
const SplitViewState.init() const SplitViewState.init()
: enabled = false, : enabled = false,
expanded = false, expanded = false,
submissionPanelWidth = null,
resizingAnimationDuration = Duration.zero,
itemScreenArgs = null; itemScreenArgs = null;
final bool enabled; final bool enabled;
final bool expanded; final bool expanded;
final double? submissionPanelWidth;
final Duration resizingAnimationDuration;
final ItemScreenArgs? itemScreenArgs; final ItemScreenArgs? itemScreenArgs;
SplitViewState copyWith({ SplitViewState copyWith({
bool? enabled, bool? enabled,
bool? expanded, bool? expanded,
double? submissionPanelWidth,
Duration? resizingAnimationDuration,
ItemScreenArgs? itemScreenArgs, ItemScreenArgs? itemScreenArgs,
}) { }) {
return SplitViewState( return SplitViewState(
enabled: enabled ?? this.enabled, enabled: enabled ?? this.enabled,
expanded: expanded ?? this.expanded, expanded: expanded ?? this.expanded,
submissionPanelWidth: submissionPanelWidth ?? this.submissionPanelWidth,
resizingAnimationDuration:
resizingAnimationDuration ?? this.resizingAnimationDuration,
itemScreenArgs: itemScreenArgs ?? this.itemScreenArgs, itemScreenArgs: itemScreenArgs ?? this.itemScreenArgs,
); );
} }
@ -32,6 +43,8 @@ class SplitViewState extends Equatable {
List<Object?> get props => <Object?>[ List<Object?> get props => <Object?>[
enabled, enabled,
expanded, expanded,
submissionPanelWidth,
resizingAnimationDuration,
itemScreenArgs, itemScreenArgs,
]; ];
} }

View File

@ -11,12 +11,19 @@ class AppException implements Exception {
} }
class RateLimitedException extends AppException { class RateLimitedException extends AppException {
RateLimitedException() : super(message: 'Rate limited...'); RateLimitedException(this.statusCode)
: super(message: 'Rate limited ($statusCode)...');
final int? statusCode;
} }
class RateLimitedWithFallbackException extends AppException { class RateLimitedWithFallbackException extends AppException {
RateLimitedWithFallbackException() RateLimitedWithFallbackException(this.statusCode)
: super(message: 'Rate limited, fetching from API instead...'); : super(
message: 'Rate limited ($statusCode), fetching from API instead...',
);
final int? statusCode;
} }
class PossibleParsingException extends AppException { class PossibleParsingException extends AppException {

View File

@ -5,10 +5,12 @@ import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio_smart_retry/dio_smart_retry.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hacki/config/constants.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/extensions/extensions.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hacki/repositories/hacker_news_repository.dart'; import 'package:hacki/repositories/hacker_news_repository.dart';
import 'package:hacki/utils/utils.dart'; import 'package:hacki/utils/utils.dart';
@ -17,13 +19,18 @@ import 'package:html/parser.dart';
import 'package:html_unescape/html_unescape.dart'; import 'package:html_unescape/html_unescape.dart';
/// For fetching anything that cannot be fetched through Hacker News API. /// For fetching anything that cannot be fetched through Hacker News API.
class HackerNewsWebRepository { class HackerNewsWebRepository with Loggable {
HackerNewsWebRepository({ HackerNewsWebRepository({
RemoteConfigCubit? remoteConfigCubit, RemoteConfigCubit? remoteConfigCubit,
HackerNewsRepository? hackerNewsRepository, HackerNewsRepository? hackerNewsRepository,
Dio? dioWithCache, Dio? dioWithCache,
Dio? dio, Dio? dio,
}) : _dio = dio ?? Dio(), }) : _dio = dio ?? Dio()
..interceptors.addAll(
<Interceptor>[
if (kDebugMode) LoggerInterceptor(),
],
),
_dioWithCache = dioWithCache ?? Dio() _dioWithCache = dioWithCache ?? Dio()
..interceptors.addAll( ..interceptors.addAll(
<Interceptor>[ <Interceptor>[
@ -34,10 +41,18 @@ class HackerNewsWebRepository {
_remoteConfigCubit = _remoteConfigCubit =
remoteConfigCubit ?? locator.get<RemoteConfigCubit>(), remoteConfigCubit ?? locator.get<RemoteConfigCubit>(),
_hackerNewsRepository = _hackerNewsRepository =
hackerNewsRepository ?? locator.get<HackerNewsRepository>(); hackerNewsRepository ?? locator.get<HackerNewsRepository>() {
_dio.interceptors.add(RetryInterceptor(dio: _dio));
}
/// The client for fetching comments. We should be careful
/// while fetching comments because it will easily trigger
/// 503 from the server.
final Dio _dioWithCache; final Dio _dioWithCache;
/// The client for fetching stories.
final Dio _dio; final Dio _dio;
final RemoteConfigCubit _remoteConfigCubit; final RemoteConfigCubit _remoteConfigCubit;
final HackerNewsRepository _hackerNewsRepository; final HackerNewsRepository _hackerNewsRepository;
@ -66,6 +81,10 @@ class HackerNewsWebRepository {
String get _moreLinkSelector => _remoteConfigCubit.state.moreLinkSelector; String get _moreLinkSelector => _remoteConfigCubit.state.moreLinkSelector;
static final Map<int, int> _next = <int, int>{}; static final Map<int, int> _next = <int, int>{};
static const List<int> _rateLimitedStatusCode = <int>[
HttpStatus.forbidden,
HttpStatus.serviceUnavailable,
];
Stream<Story> fetchStoriesStream( Stream<Story> fetchStoriesStream(
StoryType storyType, { StoryType storyType, {
@ -84,6 +103,7 @@ class HackerNewsWebRepository {
StoryType.latest => StoryType.latest =>
'$_storiesBaseUrl/${storyType.webPathParam}?next=${_next[page]}' '$_storiesBaseUrl/${storyType.webPathParam}?next=${_next[page]}'
}; };
final Uri url = Uri.parse(urlStr); final Uri url = Uri.parse(urlStr);
final Options option = Options( final Options option = Options(
headers: _headers, headers: _headers,
@ -125,8 +145,9 @@ class HackerNewsWebRepository {
(elements.elementAt(index), subtextElements.elementAt(index)), (elements.elementAt(index), subtextElements.elementAt(index)),
); );
} on DioException catch (e) { } on DioException catch (e) {
if (e.response?.statusCode == HttpStatus.forbidden) { logError('error fetching stories on page $page: $e');
throw RateLimitedWithFallbackException(); if (_rateLimitedStatusCode.contains(e.response?.statusCode)) {
throw RateLimitedWithFallbackException(e.response?.statusCode);
} }
throw GenericException(); throw GenericException();
} }
@ -260,8 +281,9 @@ class HackerNewsWebRepository {
elements.map((Element e) => int.tryParse(e.id)).whereNotNull(); elements.map((Element e) => int.tryParse(e.id)).whereNotNull();
return parsedIds; return parsedIds;
} on DioException catch (e) { } on DioException catch (e) {
if (e.response?.statusCode == HttpStatus.forbidden) { if (_rateLimitedStatusCode.contains(e.response?.statusCode)) {
throw RateLimitedException(); logError('error fetching favorites on page $page: $e');
throw RateLimitedException(e.response?.statusCode);
} }
throw GenericException(); throw GenericException();
} }
@ -338,8 +360,9 @@ class HackerNewsWebRepository {
document.querySelectorAll(_athingComtrSelector); document.querySelectorAll(_athingComtrSelector);
return elements; return elements;
} on DioException catch (e) { } on DioException catch (e) {
if (e.response?.statusCode == HttpStatus.forbidden) { if (_rateLimitedStatusCode.contains(e.response?.statusCode)) {
throw RateLimitedWithFallbackException(); logError('error fetching comments on page $page: $e');
throw RateLimitedWithFallbackException(e.response?.statusCode);
} }
throw GenericException(); throw GenericException();
} }
@ -484,4 +507,7 @@ class HackerNewsWebRepository {
) )
.trim(); .trim();
} }
@override
String get logIdentifier => 'HackerNewsWebRepository';
} }

View File

@ -1,6 +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:font_awesome_flutter/font_awesome_flutter.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';
@ -14,6 +14,8 @@ class TabletHomeScreen extends StatelessWidget {
}); });
final Widget homeScreen; final Widget homeScreen;
static const double _dragPanelWidth = Dimens.pt2;
static const double _dragDotHeight = Dimens.pt30;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -28,16 +30,26 @@ class TabletHomeScreen extends StatelessWidget {
return BlocBuilder<SplitViewCubit, SplitViewState>( return BlocBuilder<SplitViewCubit, SplitViewState>(
buildWhen: (SplitViewState previous, SplitViewState current) => buildWhen: (SplitViewState previous, SplitViewState current) =>
previous.expanded != current.expanded, previous.expanded != current.expanded ||
previous.submissionPanelWidth != current.submissionPanelWidth,
builder: (BuildContext context, SplitViewState state) { builder: (BuildContext context, SplitViewState state) {
double submissionPanelWidth =
state.submissionPanelWidth ?? homeScreenWidth;
/// Prevent overflow after orientation change.
if (submissionPanelWidth > MediaQuery.of(context).size.width) {
submissionPanelWidth =
MediaQuery.of(context).size.width - Dimens.pt64;
}
return Stack( return Stack(
children: <Widget>[ children: <Widget>[
AnimatedPositioned( AnimatedPositioned(
left: Dimens.zero, left: Dimens.zero,
top: Dimens.zero, top: Dimens.zero,
bottom: Dimens.zero, bottom: Dimens.zero,
width: homeScreenWidth, width: submissionPanelWidth,
duration: AppDurations.ms300, duration: state.resizingAnimationDuration,
curve: Curves.elasticOut, curve: Curves.elasticOut,
child: homeScreen, child: homeScreen,
), ),
@ -46,7 +58,7 @@ class TabletHomeScreen extends StatelessWidget {
left: Dimens.pt24, left: Dimens.pt24,
bottom: Dimens.pt36, bottom: Dimens.pt36,
height: Dimens.pt40, height: Dimens.pt40,
width: homeScreenWidth - Dimens.pt24, width: submissionPanelWidth - Dimens.pt48,
child: const CountdownReminder(), child: const CountdownReminder(),
) )
else else
@ -54,18 +66,74 @@ class TabletHomeScreen extends StatelessWidget {
left: Dimens.pt24, left: Dimens.pt24,
bottom: Dimens.pt36, bottom: Dimens.pt36,
height: Dimens.pt40, height: Dimens.pt40,
width: homeScreenWidth - Dimens.pt24, width: submissionPanelWidth - Dimens.pt48,
child: const DownloadProgressReminder(), child: const DownloadProgressReminder(),
), ),
AnimatedPositioned( AnimatedPositioned(
right: Dimens.zero, right: Dimens.zero,
top: Dimens.zero, top: Dimens.zero,
bottom: Dimens.zero, bottom: Dimens.zero,
left: state.expanded ? Dimens.zero : homeScreenWidth, left: state.expanded
duration: AppDurations.ms300, ? Dimens.zero
: submissionPanelWidth + _dragPanelWidth,
duration: state.resizingAnimationDuration,
curve: Curves.elasticOut, curve: Curves.elasticOut,
child: const _TabletStoryView(), child: const _TabletStoryView(),
), ),
if (!state.expanded) ...<Widget>[
Positioned(
left: submissionPanelWidth,
top: Dimens.zero,
bottom: Dimens.zero,
width: _dragPanelWidth,
child: GestureDetector(
onHorizontalDragUpdate: (DragUpdateDetails details) {
context
.read<SplitViewCubit>()
.updateSubmissionPanelWidth(
details.globalPosition.dx,
);
},
child: ColoredBox(
color: Theme.of(context).colorScheme.tertiary,
child: const SizedBox.shrink(),
),
),
),
Positioned(
left: submissionPanelWidth +
_dragPanelWidth / 2 -
_dragDotHeight / 2,
top: (MediaQuery.of(context).size.height - _dragDotHeight) /
2,
height: _dragDotHeight,
width: _dragDotHeight,
child: GestureDetector(
onHorizontalDragUpdate: (DragUpdateDetails details) {
context
.read<SplitViewCubit>()
.updateSubmissionPanelWidth(
details.globalPosition.dx,
);
},
child: Container(
width: _dragDotHeight,
height: _dragDotHeight,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.tertiary,
shape: BoxShape.circle,
),
child: Center(
child: FaIcon(
FontAwesomeIcons.gripLinesVertical,
color: Theme.of(context).colorScheme.onTertiary,
size: TextDimens.pt16,
),
),
),
),
),
],
], ],
); );
}, },

View File

@ -115,8 +115,16 @@ class LinkView extends StatelessWidget {
child: SizedBox( child: SizedBox(
height: layoutHeight, height: layoutHeight,
width: layoutHeight, width: layoutHeight,
child: CachedNetworkImage( child: imageUri == null && url.isEmpty
imageUrl: imageUri ?? '', ? FadeIn(
child: Center(
child: _HackerNewsImage(
height: layoutHeight,
),
),
)
: CachedNetworkImage(
imageUrl: imageUri ?? Constants.favicon(url),
fit: isIcon ? BoxFit.scaleDown : BoxFit.fitWidth, fit: isIcon ? BoxFit.scaleDown : BoxFit.fitWidth,
cacheKey: imageUri, cacheKey: imageUri,
errorWidget: (_, __, ___) { errorWidget: (_, __, ___) {

View File

@ -13,6 +13,7 @@ abstract class Dimens {
static const double pt18 = 18; static const double pt18 = 18;
static const double pt20 = 20; static const double pt20 = 20;
static const double pt24 = 24; static const double pt24 = 24;
static const double pt30 = 30;
static const double pt36 = 36; static const double pt36 = 36;
static const double pt40 = 40; static const double pt40 = 40;
static const double pt48 = 48; static const double pt48 = 48;

View File

@ -254,6 +254,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.6.0" version: "5.6.0"
dio_smart_retry:
dependency: "direct main"
description:
name: dio_smart_retry
sha256: "3d71450c19b4d91ef4c7d726a55a284bfc11eb3634f1f25006cdfab3f8595653"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
dio_web_adapter: dio_web_adapter:
dependency: transitive dependency: transitive
description: description:
@ -1421,10 +1429,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.4" version: "14.2.5"
wakelock_plus: wakelock_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1571,4 +1579,4 @@ packages:
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.4.0 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.24.3"

View File

@ -1,11 +1,11 @@
name: hacki name: hacki
description: A Hacker News reader. description: A Hacker News reader.
version: 2.9.0+148 version: 2.9.2+150
publish_to: none publish_to: none
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"
flutter: "3.24.0" flutter: "3.24.3"
dependencies: dependencies:
adaptive_theme: ^3.2.0 adaptive_theme: ^3.2.0
@ -18,6 +18,7 @@ dependencies:
connectivity_plus: ^6.0.3 connectivity_plus: ^6.0.3
device_info_plus: ^10.1.0 device_info_plus: ^10.1.0
dio: ^5.4.3+1 dio: ^5.4.3+1
dio_smart_retry: ^6.0.0
equatable: ^2.0.5 equatable: ^2.0.5
fast_gbk: ^1.0.0 fast_gbk: ^1.0.0
feature_discovery: feature_discovery: