Compare commits

...

6 Commits

Author SHA1 Message Date
a2c66a0075 fixed background color of nav bar on Android. (#72) 2022-09-17 00:44:27 -07:00
5f43fd6968 v0.2.31 (#71)
* bumped version.

* bumped version.

* bumped version

* bumped version.

* downgrade flutter version.

* Use transparent color for navigation bar (#70)

* Use transparent color for navigation bar

* Fixed whitespace

Co-authored-by: Niklas Weinhart <git@weinhart.net>

* bumped ios version.

* bumped versions.

* updated min screen width.

* removed print.

* fixed stack overflow in EditCubit.

* added integration test.

* bumped version.

Co-authored-by: wnhrt <github@weinhart.net>
Co-authored-by: Niklas Weinhart <git@weinhart.net>
2022-09-16 22:37:16 -07:00
d83381a7fd v0.2.30 (#68)
* bumped version.

* cache  in case app gets killed in the background.

* bumped flutter version.

* updated

* improved 'ReplyBox'.

* fixed lint.

* fixed EditCubit.

* updated EditCubit.

* fixed navigation in tablet mode.

* clear cache after submission.
2022-08-05 22:13:30 -07:00
764ff09345 v0.2.29-hotfix (#67)
* fixed datetime extension.

* updated README.md
2022-07-06 20:37:18 -07:00
ab449adce2 v0.2.29 (#66)
* fixed stuff.

* fixed UI.

* bumped version.
2022-07-06 17:04:14 -07:00
2ec41b26f2 updated README.md 2022-07-06 16:23:05 -07:00
37 changed files with 537 additions and 243 deletions

View File

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
JAVA_VERSION: "11.0" JAVA_VERSION: "11.0"
FLUTTER_VERSION: "3.0.3" FLUTTER_VERSION: "3.3.2"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v2 - uses: actions/setup-java@v2
@ -20,7 +20,7 @@ jobs:
java-version: '17' java-version: '17'
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.0.3' flutter-version: '3.3.2'
channel: 'stable' channel: 'stable'
- run: flutter pub get - run: flutter pub get
- run: flutter analyze - run: flutter analyze

View File

@ -1,14 +1,13 @@
# <img width="64" src="https://user-images.githubusercontent.com/7277662/167775086-0b234f28-dee4-44f6-aae4-14a28ed4bbb6.png"> Hacki for Hacker News # <img width="64" src="https://user-images.githubusercontent.com/7277662/167775086-0b234f28-dee4-44f6-aae4-14a28ed4bbb6.png"> Hacki for Hacker News
A simple noiseless [Hacker News](https://news.ycombinator.com/) client made with Flutter that is just enough. A [Hacker News](https://news.ycombinator.com/) client made with Flutter that is just enough.
[![App Store](https://img.shields.io/itunes/v/1602043763?label=App%20Store)](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone) [![App Store](https://img.shields.io/itunes/v/1602043763?label=App%20Store)](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone)
[![Fdroid version](https://img.shields.io/f-droid/v/com.jiaqifeng.hacki)](https://f-droid.org/en/packages/com.jiaqifeng.hacki/) [![Fdroid version](https://img.shields.io/f-droid/v/com.jiaqifeng.hacki)](https://f-droid.org/en/packages/com.jiaqifeng.hacki/)
[![GH version](https://img.shields.io/github/release/livinglist/hacki.svg?logo=github)](https://github.com/Livinglist/Hacki/releases/latest) [![GH version](https://img.shields.io/github/release/livinglist/hacki.svg?logo=github)](https://github.com/Livinglist/Hacki/releases/latest)
[![Visits Badge](https://badges.pufler.dev/visits/livinglist/Hacki)](https://badges.pufler.dev)
[![GitHub](https://img.shields.io/github/stars/livinglist/Hacki?style=social)](https://img.shields.io/github/stars/livinglist/Hacki?style=social)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![GitHub](https://img.shields.io/github/stars/livinglist/Hacki?style=social)](https://img.shields.io/github/stars/livinglist/Hacki?style=social)
[<img src="assets/images/app_store_badge.png" height="50">](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone) [<img src="assets/images/google_play_badge.png" height="50">](https://play.google.com/store/apps/details?id=com.jiaqifeng.hacki&hl=en_US&gl=US) [<img src="assets/images/f_droid_badge.png" height="50">](https://f-droid.org/en/packages/com.jiaqifeng.hacki/) [<img src="assets/images/app_store_badge.png" height="50">](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone) [<img src="assets/images/google_play_badge.png" height="50">](https://play.google.com/store/apps/details?id=com.jiaqifeng.hacki&hl=en_US&gl=US) [<img src="assets/images/f_droid_badge.png" height="50">](https://f-droid.org/en/packages/com.jiaqifeng.hacki/)

View File

@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
android { android {
compileSdkVersion 32 compileSdkVersion 33
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -51,7 +51,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.jiaqifeng.hacki" applicationId "com.jiaqifeng.hacki"
minSdkVersion 26 minSdkVersion 26
targetSdkVersion 32 targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }

View File

@ -0,0 +1,3 @@
- Lazy loading.
- Offline mode now includes web pages.
- You can now sort comments in story screen.

View File

@ -0,0 +1,3 @@
- Lazy loading.
- Offline mode now includes web pages.
- You can now sort comments in story screen.

View File

@ -0,0 +1,3 @@
- Lazy loading.
- Offline mode now includes web pages.
- You can now sort comments in story screen.

View File

@ -0,0 +1,2 @@
- Bumped Flutter version.
- Updated navigation bar background color.

View File

@ -1,46 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:hacki/main.dart' as app;
import 'package:hacki/screens/widgets/story_tile.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('performance test', () {
testWidgets('scrolling performance on ItemScreen',
(WidgetTester tester) async {
await app.main(testing: true);
await tester.pump();
final Finder bestStoryTabFinder = find.text('BEST');
await tester.tap(bestStoryTabFinder);
await tester.pumpAndSettle(const Duration(seconds: 3));
final Finder storyTileFinder = find.byType(StoryTile);
await tester.tap(storyTileFinder.first);
await tester.pumpAndSettle(const Duration(seconds: 3));
TestGesture gesture = await tester.startGesture(const Offset(0, 300));
await gesture.moveBy(const Offset(0, -300));
await tester.pump();
gesture = await tester.startGesture(const Offset(0, 300));
await gesture.moveBy(const Offset(0, -300));
await tester.pump();
gesture = await tester.startGesture(const Offset(0, 300));
await gesture.moveBy(const Offset(0, -300));
await tester.pump();
gesture = await tester.startGesture(const Offset(0, 300));
await gesture.moveBy(const Offset(0, 900));
await tester.pump();
gesture = await tester.startGesture(const Offset(0, 300));
await gesture.moveBy(const Offset(0, -900));
await tester.pump();
});
});
}

View File

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hacki/main.dart' as app;
import 'package:hacki/screens/widgets/widgets.dart';
import 'package:integration_test/integration_test.dart';
void main() {
final IntegrationTestWidgetsFlutterBinding binding =
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Scrolling test', (WidgetTester tester) async {
await app.main(testing: true);
await tester.pumpAndSettle();
final Finder bestTabFinder = find.widgetWithText(Tab, 'BEST');
expect(bestTabFinder, findsOneWidget);
Future<void> scrollDown(WidgetTester tester) async {
await tester.timedDragFrom(
const Offset(100, 200),
const Offset(100, -700),
const Duration(seconds: 2),
);
await tester.pump();
}
Future<void> scrollUp(WidgetTester tester) async {
await tester.timedDragFrom(
const Offset(100, 200),
const Offset(100, 700),
const Duration(seconds: 1),
);
await tester.pump();
}
await binding.traceAction(
() async {
await tester.tap(bestTabFinder);
await tester.pump();
const int count = 10;
for (int i = 0; i < count; i++) {
await scrollDown(tester);
}
for (int i = 0; i < count - 3; i++) {
await scrollUp(tester);
}
await tester.pumpAndSettle(const Duration(seconds: 2));
final Finder storyFinder = find.byType(StoryTile);
expect(storyFinder, findsWidgets);
final Finder firstStoryFinder = storyFinder.first;
expect(firstStoryFinder, findsOneWidget);
await tester.tap(firstStoryFinder);
await tester.pump(const Duration(seconds: 4));
},
reportKey: 'scrolling_timeline',
);
});
}

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>9.0</string> <string>11.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
platform :ios, '13.0' # platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@ -37,8 +37,5 @@ end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target) flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end end
end end

View File

@ -108,7 +108,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
@ -128,6 +128,6 @@ SPEC CHECKSUMS:
webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
PODFILE CHECKSUM: e4c97c7a9aacaeda4b952f7ef9ea29e47660f622 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.11.2 COCOAPODS: 1.11.2

View File

@ -549,7 +549,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -568,16 +568,16 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = QMWX3X2NF7; DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 0.2.28; MARKETING_VERSION = 0.2.31;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki; PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -635,7 +635,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -684,7 +684,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -705,16 +705,16 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = QMWX3X2NF7; DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 0.2.28; MARKETING_VERSION = 0.2.31;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki; PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -736,16 +736,16 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = QMWX3X2NF7; DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 0.2.28; MARKETING_VERSION = 0.2.31;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki; PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

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/locator.dart'; import 'package:hacki/config/locator.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hacki/services/services.dart'; import 'package:hacki/services/services.dart';
import 'package:hacki/utils/debouncer.dart'; import 'package:hacki/utils/debouncer.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
part 'edit_state.dart'; part 'edit_state.dart';
class EditCubit extends Cubit<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: const Duration(seconds: 1)),
@ -47,6 +48,7 @@ class EditCubit extends Cubit<EditState> {
_draftCache.removeDraft(replyingTo: state.replyingTo!.id); _draftCache.removeDraft(replyingTo: state.replyingTo!.id);
} }
emit(const EditState.init()); emit(const EditState.init());
clear();
} }
void onTextChanged(String text) { void onTextChanged(String text) {
@ -61,4 +63,54 @@ class EditCubit extends Cubit<EditState> {
}); });
} }
} }
void deleteDraft() => clear();
bool called = false;
@override
EditState? fromJson(Map<String, dynamic> json) {
final String text = json['text'] as String? ?? '';
final Map<String, dynamic>? itemJson =
json['item'] as Map<String, dynamic>?;
final Item? replyingTo = itemJson == null ? null : Item.fromJson(itemJson);
if (replyingTo != null && text.isNotEmpty) {
_draftCache.cacheDraft(text: text, replyingTo: replyingTo.id);
final EditState state = EditState(
text: text,
replyingTo: replyingTo,
);
_cachedState = state;
return state;
}
return state;
}
@override
Map<String, dynamic>? toJson(EditState state) {
EditState selected = state;
if (state.replyingTo == null ||
(state.replyingTo?.id != _cachedState.replyingTo?.id &&
state.text.isNullOrEmpty)) {
selected = _cachedState;
}
if (selected.text.isNullOrEmpty) {
clear();
return null;
}
return <String, dynamic>{
'text': selected.text ?? '',
'item': selected.replyingTo?.toJson(),
};
}
static EditState _cachedState = const EditState.init();
} }

View File

@ -33,7 +33,8 @@ class NotificationCubit extends Cubit<NotificationState> {
_preferenceRepository.shouldShowNotification _preferenceRepository.shouldShowNotification
.then((bool showNotification) { .then((bool showNotification) {
if (showNotification) { if (showNotification) {
init(); // Delaying the initialization to prevent janks in home screen.
Future<void>.delayed(const Duration(seconds: 2), init);
} }
}); });

View File

@ -1,5 +1,6 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/services.dart';
import 'package:hacki/config/locator.dart'; import 'package:hacki/config/locator.dart';
import 'package:hacki/cubits/comments/comments_cubit.dart'; import 'package:hacki/cubits/comments/comments_cubit.dart';
import 'package:hacki/repositories/repositories.dart'; import 'package:hacki/repositories/repositories.dart';
@ -82,12 +83,14 @@ class PreferenceCubit extends Cubit<PreferenceState> {
void selectFetchMode(FetchMode? fetchMode) { void selectFetchMode(FetchMode? fetchMode) {
if (fetchMode == null || state.fetchMode == fetchMode) return; if (fetchMode == null || state.fetchMode == fetchMode) return;
HapticFeedback.lightImpact();
emit(state.copyWith(fetchMode: fetchMode)); emit(state.copyWith(fetchMode: fetchMode));
_preferenceRepository.selectFetchMode(fetchMode); _preferenceRepository.selectFetchMode(fetchMode);
} }
void selectCommentsOrder(CommentsOrder? order) { void selectCommentsOrder(CommentsOrder? order) {
if (order == null || state.order == order) return; if (order == null || state.order == order) return;
HapticFeedback.lightImpact();
emit(state.copyWith(order: order)); emit(state.copyWith(order: order));
_preferenceRepository.selectCommentsOrder(order); _preferenceRepository.selectCommentsOrder(order);
} }

View File

@ -3,7 +3,7 @@ import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
extension TryReadContext on BuildContext { extension ContextExtension on BuildContext {
T? tryRead<T>() { T? tryRead<T>() {
try { try {
return read<T>(); return read<T>();
@ -22,7 +22,7 @@ extension TryReadContext on BuildContext {
static double _screenWidth = 0; static double _screenWidth = 0;
static double _storyTileHeight = 0; static double _storyTileHeight = 0;
static int _storyTileMaxLines = 4; static int _storyTileMaxLines = 4;
static const double _screenWidthLowerBound = 428, static const double _screenWidthLowerBound = 430,
_screenWidthUpperBound = 850, _screenWidthUpperBound = 850,
_picHeightLowerBound = 110, _picHeightLowerBound = 110,
_picHeightUpperBound = 128, _picHeightUpperBound = 128,

View File

@ -7,10 +7,8 @@ extension DateTimeExtension on DateTime {
return '$gap year${gap == 1 ? '' : 's'} ago'; return '$gap year${gap == 1 ? '' : 's'} ago';
} else if (diff.inDays > 30) { } else if (diff.inDays > 30) {
int gap = now.month - month; int gap = now.month - month;
if (gap == 0) { if (gap <= 0) {
gap = 1; gap = now.month + 12 - month;
} else if (gap < 0) {
gap = now.month + (12 - month);
} }
return '$gap month${gap == 1 ? '' : 's'} ago'; return '$gap month${gap == 1 ? '' : 's'} ago';
} else if (diff.inDays >= 1) { } else if (diff.inDays >= 1) {

View File

@ -13,3 +13,12 @@ extension StringExtension on String {
return replaceAllMapped(regex, (_) => ''); return replaceAllMapped(regex, (_) => '');
} }
} }
extension OptionalStringExtension on String? {
bool get isNullOrEmpty {
if (this == null) return true;
return this!.trim().isEmpty;
}
bool get isNotNullOrEmpty => !isNullOrEmpty;
}

View File

@ -6,6 +6,7 @@ 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/scheduler.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';
import 'package:flutter_siri_suggestions/flutter_siri_suggestions.dart'; import 'package:flutter_siri_suggestions/flutter_siri_suggestions.dart';
@ -19,6 +20,7 @@ import 'package:hacki/services/custom_bloc_observer.dart';
import 'package:hacki/services/fetcher.dart'; import 'package:hacki/services/fetcher.dart';
import 'package:hacki/styles/styles.dart'; import 'package:hacki/styles/styles.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:rxdart/rxdart.dart' show BehaviorSubject; import 'package:rxdart/rxdart.dart' show BehaviorSubject;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -39,6 +41,12 @@ Future<void> main({bool testing = false}) async {
isTesting = testing; isTesting = testing;
final HydratedStorage storage = await HydratedStorage.build(
storageDirectory: kIsWeb
? HydratedStorage.webStorageDirectory
: await getTemporaryDirectory(),
);
if (Platform.isIOS) { if (Platform.isIOS) {
unawaited( unawaited(
Workmanager().initialize( Workmanager().initialize(
@ -79,6 +87,19 @@ Future<void> main({bool testing = false}) async {
siriSuggestionSubject.add(storyId); siriSuggestionSubject.add(storyId);
}, },
); );
} else if (Platform.isAndroid) {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent,
),
);
await SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge,
overlays: <SystemUiOverlay>[SystemUiOverlay.top],
);
} }
final Directory tempDir = await getTemporaryDirectory(); final Directory tempDir = await getTemporaryDirectory();
@ -92,26 +113,15 @@ Future<void> main({bool testing = false}) async {
final bool trueDarkMode = final bool trueDarkMode =
prefs.getBool(PreferenceRepository.trueDarkModeKey) ?? false; prefs.getBool(PreferenceRepository.trueDarkModeKey) ?? false;
if (kReleaseMode) { Bloc.observer = CustomBlocObserver();
HydratedBloc.storage = storage;
runApp( runApp(
HackiApp( HackiApp(
savedThemeMode: savedThemeMode, savedThemeMode: savedThemeMode,
trueDarkMode: trueDarkMode, trueDarkMode: trueDarkMode,
), ),
); );
} else {
BlocOverrides.runZoned(
() {
runApp(
HackiApp(
savedThemeMode: savedThemeMode,
trueDarkMode: trueDarkMode,
),
);
},
blocObserver: CustomBlocObserver(),
);
}
} }
class HackiApp extends StatelessWidget { class HackiApp extends StatelessWidget {
@ -187,6 +197,10 @@ class HackiApp extends StatelessWidget {
lazy: false, lazy: false,
create: (BuildContext context) => PostCubit(), create: (BuildContext context) => PostCubit(),
), ),
BlocProvider<EditCubit>(
lazy: false,
create: (BuildContext context) => EditCubit(),
),
], ],
child: AdaptiveTheme( child: AdaptiveTheme(
light: ThemeData( light: ThemeData(

View File

@ -59,6 +59,7 @@ class Comment extends Item {
); );
} }
@override
Map<String, dynamic> toJson() => <String, dynamic>{ Map<String, dynamic> toJson() => <String, dynamic>{
'id': id, 'id': id,
'time': time, 'time': time,

View File

@ -1,7 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:hacki/extensions/date_time_extension.dart'; import 'package:hacki/extensions/date_time_extension.dart';
abstract class Item extends Equatable { class Item extends Equatable {
const Item({ const Item({
required this.id, required this.id,
required this.deleted, required this.deleted,
@ -35,6 +35,22 @@ abstract class Item extends Equatable {
text = '', text = '',
type = ''; type = '';
Item.fromJson(Map<String, dynamic> json)
: id = json['id'] as int? ?? 0,
score = json['score'] as int? ?? 0,
descendants = json['descendants'] as int? ?? 0,
time = json['time'] as int? ?? 0,
by = json['by'] as String? ?? '',
title = json['title'] as String? ?? '',
text = json['text'] as String? ?? '',
url = json['url'] as String? ?? '',
kids = <int>[],
dead = json['dead'] as bool? ?? false,
deleted = json['deleted'] as bool? ?? false,
parent = json['parent'] as int? ?? 0,
parts = <int>[],
type = json['type'] as String? ?? '';
final int id; final int id;
final int time; final int time;
final int score; final int score;
@ -65,4 +81,40 @@ abstract class Item extends Equatable {
bool get isJob => type == 'job'; bool get isJob => type == 'job';
bool get isComment => type == 'comment'; bool get isComment => type == 'comment';
Map<String, dynamic> toJson() {
return <String, dynamic>{
'descendants': descendants,
'id': id,
'score': score,
'time': time,
'by': by,
'title': title,
'url': url,
'kids': kids,
'text': text,
'dead': dead,
'deleted': deleted,
'type': type,
'parts': parts,
};
}
@override
List<Object?> get props => <Object?>[
id,
deleted,
by,
time,
text,
dead,
parent,
kids,
url,
score,
title,
type,
parts,
descendants,
];
} }

View File

@ -79,6 +79,7 @@ class PollOption extends Item {
); );
} }
@override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return <String, dynamic>{ return <String, dynamic>{
'descendants': descendants, 'descendants': descendants,

View File

@ -91,6 +91,7 @@ class Story extends Item {
String get simpleMetadata => String get simpleMetadata =>
'''$score point${score > 1 ? 's' : ''} $descendants comment${descendants > 1 ? 's' : ''} $postedDate'''; '''$score point${score > 1 ? 's' : ''} $descendants comment${descendants > 1 ? 's' : ''} $postedDate''';
@override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return <String, dynamic>{ return <String, dynamic>{
'descendants': descendants, 'descendants': descendants,

View File

@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
class User { class User {
@ -39,8 +37,6 @@ class User {
@override @override
String toString() { String toString() {
final String prettyString = return 'User $about, $created, $delay, $id, $karma';
const JsonEncoder.withIndent(' ').convert(this);
return 'User $prettyString';
} }
} }

View File

@ -96,10 +96,6 @@ class ItemScreen extends StatefulWidget {
useCommentCache: args.useCommentCache, useCommentCache: args.useCommentCache,
), ),
), ),
BlocProvider<EditCubit>(
lazy: false,
create: (BuildContext context) => EditCubit(),
),
], ],
child: ItemScreen( child: ItemScreen(
item: args.item, item: args.item,
@ -141,10 +137,6 @@ class ItemScreen extends StatefulWidget {
targetParents: args.targetComments, targetParents: args.targetComments,
), ),
), ),
BlocProvider<EditCubit>(
lazy: false,
create: (BuildContext context) => EditCubit(),
),
], ],
child: ItemScreen( child: ItemScreen(
item: args.item, item: args.item,
@ -164,7 +156,7 @@ class ItemScreen extends StatefulWidget {
_ItemScreenState createState() => _ItemScreenState(); _ItemScreenState createState() => _ItemScreenState();
} }
class _ItemScreenState extends State<ItemScreen> { class _ItemScreenState extends State<ItemScreen> with RouteAware {
final TextEditingController commentEditingController = final TextEditingController commentEditingController =
TextEditingController(); TextEditingController();
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
@ -185,11 +177,20 @@ class _ItemScreenState extends State<ItemScreen> {
static const Duration _featureDiscoveryDismissThrottleDelay = static const Duration _featureDiscoveryDismissThrottleDelay =
Duration(seconds: 1); Duration(seconds: 1);
@override
void didPop() {
super.didPop();
if (context.read<EditCubit>().state.text.isNullOrEmpty) {
context.read<EditCubit>().onReplyBoxClosed();
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance
..addPostFrameCallback((_) {
if (!isTesting) { if (!isTesting) {
FeatureDiscovery.discoverFeatures( FeatureDiscovery.discoverFeatures(
context, context,
@ -200,6 +201,15 @@ class _ItemScreenState extends State<ItemScreen> {
}, },
); );
} }
})
..addPostFrameCallback((_) {
final ModalRoute<dynamic>? route = ModalRoute.of(context);
if (route == null) return;
locator
.get<RouteObserver<ModalRoute<dynamic>>>()
.subscribe(this, route);
}); });
scrollController.addListener(() { scrollController.addListener(() {
@ -208,6 +218,8 @@ class _ItemScreenState extends State<ItemScreen> {
context.read<EditCubit>().onScrolled(); context.read<EditCubit>().onScrolled();
} }
}); });
commentEditingController.text = context.read<EditCubit>().state.text ?? '';
} }
@override @override
@ -502,7 +514,7 @@ class _ItemScreenState extends State<ItemScreen> {
Text( Text(
'''${state.item.score} karma, ${state.item.descendants} comment${state.item.descendants > 1 ? 's' : ''}''', '''${state.item.score} karma, ${state.item.descendants} comment${state.item.descendants > 1 ? 's' : ''}''',
style: const TextStyle( style: const TextStyle(
fontSize: TextDimens.pt12, fontSize: TextDimens.pt13,
), ),
), ),
] else ...<Widget>[ ] else ...<Widget>[
@ -526,7 +538,7 @@ class _ItemScreenState extends State<ItemScreen> {
: const Text( : const Text(
'View parent thread', 'View parent thread',
style: TextStyle( style: TextStyle(
fontSize: TextDimens.pt12, fontSize: TextDimens.pt13,
), ),
), ),
), ),
@ -542,7 +554,7 @@ class _ItemScreenState extends State<ItemScreen> {
child: Text( child: Text(
'Lazy', 'Lazy',
style: TextStyle( style: TextStyle(
fontSize: TextDimens.pt12, fontSize: TextDimens.pt13,
), ),
), ),
), ),
@ -551,7 +563,7 @@ class _ItemScreenState extends State<ItemScreen> {
child: Text( child: Text(
'Eager', 'Eager',
style: TextStyle( style: TextStyle(
fontSize: TextDimens.pt12, fontSize: TextDimens.pt13,
), ),
), ),
), ),
@ -573,7 +585,7 @@ class _ItemScreenState extends State<ItemScreen> {
child: Text( child: Text(
'Natural', 'Natural',
style: TextStyle( style: TextStyle(
fontSize: TextDimens.pt12, fontSize: TextDimens.pt13,
), ),
), ),
), ),
@ -582,7 +594,7 @@ class _ItemScreenState extends State<ItemScreen> {
child: Text( child: Text(
'Newest first', 'Newest first',
style: TextStyle( style: TextStyle(
fontSize: TextDimens.pt12, fontSize: TextDimens.pt13,
), ),
), ),
), ),
@ -591,7 +603,7 @@ class _ItemScreenState extends State<ItemScreen> {
child: Text( child: Text(
'Oldest first', 'Oldest first',
style: TextStyle( style: TextStyle(
fontSize: TextDimens.pt12, fontSize: TextDimens.pt13,
), ),
), ),
), ),
@ -685,7 +697,8 @@ class _ItemScreenState extends State<ItemScreen> {
return BlocListener<EditCubit, EditState>( return BlocListener<EditCubit, EditState>(
listenWhen: (EditState previous, EditState current) { listenWhen: (EditState previous, EditState current) {
return previous.replyingTo != current.replyingTo || return previous.replyingTo != current.replyingTo ||
previous.itemBeingEdited != current.itemBeingEdited; previous.itemBeingEdited != current.itemBeingEdited ||
commentEditingController.text != current.text;
}, },
listener: (BuildContext context, EditState editState) { listener: (BuildContext context, EditState editState) {
if (editState.replyingTo != null || if (editState.replyingTo != null ||

View File

@ -5,7 +5,9 @@ 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:flutter_linkify/flutter_linkify.dart'; import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:hacki/cubits/cubits.dart'; import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/item.dart'; import 'package:hacki/models/item.dart';
import 'package:hacki/screens/screens.dart';
import 'package:hacki/styles/styles.dart'; import 'package:hacki/styles/styles.dart';
import 'package:hacki/utils/link_util.dart'; import 'package:hacki/utils/link_util.dart';
@ -145,6 +147,39 @@ class _ReplyBoxState extends State<ReplyBox> {
color: Palette.orange, color: Palette.orange,
), ),
onPressed: () { onPressed: () {
final EditState state =
context.read<EditCubit>().state;
if (state.replyingTo != null &&
state.text.isNotNullOrEmpty) {
showDialog<void>(
context: context,
builder: (BuildContext context) =>
AlertDialog(
title: const Text('Save draft?'),
actions: <Widget>[
TextButton(
onPressed: () {
context
.read<EditCubit>()
.deleteDraft();
Navigator.pop(context);
},
child: const Text(
'No',
style: TextStyle(
color: Palette.red,
),
),
),
TextButton(
onPressed: () =>
Navigator.pop(context),
child: const Text('Yes'),
),
],
),
);
}
widget.onCloseTapped(); widget.onCloseTapped();
expanded = false; expanded = false;
}, },
@ -222,7 +257,6 @@ class _ReplyBoxState extends State<ReplyBox> {
showDialog<void>( showDialog<void>(
context: context, context: context,
barrierDismissible: true,
builder: (_) { builder: (_) {
return AlertDialog( return AlertDialog(
insetPadding: const EdgeInsets.symmetric( insetPadding: const EdgeInsets.symmetric(
@ -249,8 +283,31 @@ class _ReplyBoxState extends State<ReplyBox> {
style: const TextStyle(color: Palette.grey), style: const TextStyle(color: Palette.grey),
), ),
const Spacer(), const Spacer(),
if (replyingTo != null)
TextButton( TextButton(
child: const Text('Copy All'), child: const Text('View thread'),
onPressed: () {
HapticFeedback.lightImpact();
setState(() {
expanded = false;
});
Navigator.popUntil(
context,
(Route<dynamic> route) =>
route.settings.name == ItemScreen.routeName ||
route.isFirst,
);
goToItemScreen(
args: ItemScreenArgs(
item: replyingTo,
useCommentCache: true,
),
forceNewScreen: true,
);
},
),
TextButton(
child: const Text('Copy all'),
onPressed: () => FlutterClipboard.copy( onPressed: () => FlutterClipboard.copy(
replyingTo?.text ?? '', replyingTo?.text ?? '',
).then((_) => HapticFeedback.selectionClick()), ).then((_) => HapticFeedback.selectionClick()),

View File

@ -20,7 +20,7 @@ class _ScrollUpIconButtonState extends State<ScrollUpIconButton> {
super.initState(); super.initState();
widget.scrollController.addListener(() { widget.scrollController.addListener(() {
if (widget.scrollController.offset <= 1000) { if (widget.scrollController.offset <= 1000 && mounted) {
setState(() {}); setState(() {});
} }
}); });

View File

@ -526,7 +526,7 @@ class _ProfileScreenState extends State<ProfileScreen>
showAboutDialog( showAboutDialog(
context: context, context: context,
applicationName: 'Hacki', applicationName: 'Hacki',
applicationVersion: 'v0.2.28', applicationVersion: 'v0.2.31',
applicationIcon: ClipRRect( applicationIcon: ClipRRect(
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular( Radius.circular(

View File

@ -289,8 +289,21 @@ class CommentTile extends StatelessWidget {
.read<CommentsCubit>() .read<CommentsCubit>()
.state .state
.commentIds .commentIds
.contains(comment.kids.first)) .contains(comment.kids.first) &&
!context
.read<CommentsCubit>()
.state
.onlyShowTargetComment)
Center( Center(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Dimens.pt12,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
@ -306,6 +319,10 @@ class CommentTile extends StatelessWidget {
), ),
), ),
), ),
],
),
),
),
const Divider( const Divider(
height: Dimens.zero, height: Dimens.zero,
), ),

View File

@ -33,7 +33,7 @@ class _CountDownReminderState extends State<CountdownReminder>
bool isVisible = false; bool isVisible = false;
static const Duration countdownDuration = Duration(seconds: 8); static const Duration countdownDuration = Duration(seconds: 8);
static const Duration visibilityCountdownDuration = Duration(seconds: 3); static const Duration visibilityCountdownDuration = Duration.zero;
@override @override
void initState() { void initState() {

View File

@ -86,7 +86,7 @@ class _OnboardingViewState extends State<OnboardingView> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
shape: const CircleBorder(), shape: const CircleBorder(),
primary: Palette.orange, backgroundColor: Palette.orange,
padding: const EdgeInsets.all( padding: const EdgeInsets.all(
Dimens.pt18, Dimens.pt18,
), ),

View File

@ -1,11 +1,16 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:hacki/blocs/blocs.dart';
import 'package:hacki/config/locator.dart'; import 'package:hacki/config/locator.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
class CustomBlocObserver extends BlocObserver { class CustomBlocObserver extends BlocObserver {
@override @override
void onCreate(BlocBase<dynamic> bloc) { void onCreate(BlocBase<dynamic> bloc) {
if (bloc is! CollapseCubit) {
locator.get<Logger>().v('$bloc created'); locator.get<Logger>().v('$bloc created');
}
super.onCreate(bloc); super.onCreate(bloc);
} }
@ -14,7 +19,10 @@ class CustomBlocObserver extends BlocObserver {
Bloc<dynamic, dynamic> bloc, Bloc<dynamic, dynamic> bloc,
Object? event, Object? event,
) { ) {
if (event is! StoriesEvent) {
locator.get<Logger>().v(event); locator.get<Logger>().v(event);
}
super.onEvent(bloc, event); super.onEvent(bloc, event);
} }
@ -23,7 +31,10 @@ class CustomBlocObserver extends BlocObserver {
Bloc<dynamic, dynamic> bloc, Bloc<dynamic, dynamic> bloc,
Transition<dynamic, dynamic> transition, Transition<dynamic, dynamic> transition,
) { ) {
if (bloc is! StoriesBloc) {
locator.get<Logger>().v(transition); locator.get<Logger>().v(transition);
}
super.onTransition(bloc, transition); super.onTransition(bloc, transition);
} }
@ -34,6 +45,8 @@ class CustomBlocObserver extends BlocObserver {
StackTrace stackTrace, StackTrace stackTrace,
) { ) {
locator.get<Logger>().e(error); locator.get<Logger>().e(error);
locator.get<Logger>().e(stackTrace);
super.onError(bloc, error, stackTrace); super.onError(bloc, error, stackTrace);
} }
} }

View File

@ -27,6 +27,7 @@ abstract class TextDimens {
static const double pt8 = 8; static const double pt8 = 8;
static const double pt10 = 10; static const double pt10 = 10;
static const double pt12 = 12; static const double pt12 = 12;
static const double pt13 = 13;
static const double pt14 = 14; static const double pt14 = 14;
static const double pt15 = 15; static const double pt15 = 15;
static const double pt16 = 16; static const double pt16 = 16;

View File

@ -7,28 +7,28 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "40.0.0" version: "47.0.0"
adaptive_theme: adaptive_theme:
dependency: "direct main" dependency: "direct main"
description: description:
name: adaptive_theme name: adaptive_theme
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.1"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.1.0" version: "4.7.0"
archive: archive:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.11" version: "3.3.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -42,7 +42,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.9.0"
badges: badges:
dependency: "direct main" dependency: "direct main"
description: description:
@ -56,14 +56,14 @@ packages:
name: bloc name: bloc
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.0.3" version: "8.1.0"
bloc_test: bloc_test:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: bloc_test name: bloc_test
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "9.0.3" version: "9.1.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -77,35 +77,28 @@ packages:
name: cached_network_image name: cached_network_image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.1" version: "3.2.2"
cached_network_image_platform_interface: cached_network_image_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: cached_network_image_platform_interface name: cached_network_image_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "2.0.0"
cached_network_image_web: cached_network_image_web:
dependency: transitive dependency: transitive
description: description:
name: cached_network_image_web name: cached_network_image_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.2"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clipboard: clipboard:
dependency: "direct main" dependency: "direct main"
description: description:
@ -119,7 +112,7 @@ packages:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: "direct main" dependency: "direct main"
description: description:
@ -133,7 +126,7 @@ packages:
name: connectivity_plus name: connectivity_plus
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.5" version: "2.3.7"
connectivity_plus_linux: connectivity_plus_linux:
dependency: transitive dependency: transitive
description: description:
@ -161,7 +154,7 @@ packages:
name: connectivity_plus_web name: connectivity_plus_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.2" version: "1.2.3"
connectivity_plus_windows: connectivity_plus_windows:
dependency: transitive dependency: transitive
description: description:
@ -182,14 +175,14 @@ packages:
name: coverage name: coverage
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.5.0"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@ -203,7 +196,7 @@ packages:
name: dbus name: dbus
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.6" version: "0.7.8"
diff_match_patch: diff_match_patch:
dependency: transitive dependency: transitive
description: description:
@ -224,14 +217,14 @@ packages:
name: equatable name: equatable
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.3" version: "2.0.5"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
fast_gbk: fast_gbk:
dependency: "direct main" dependency: "direct main"
description: description:
@ -273,7 +266,7 @@ packages:
name: flutter_bloc name: flutter_bloc
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.0.1" version: "8.1.1"
flutter_blurhash: flutter_blurhash:
dependency: transitive dependency: transitive
description: description:
@ -310,9 +303,11 @@ packages:
flutter_inappwebview: flutter_inappwebview:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_inappwebview path: "."
url: "https://pub.dartlang.org" ref: master
source: hosted resolved-ref: e81d9b76aa8cbbdec65429a84a9edaf370715e90
url: "https://github.com/vocsyinfotech/flutter_inappwebview"
source: git
version: "5.4.3+7" version: "5.4.3+7"
flutter_linkify: flutter_linkify:
dependency: "direct main" dependency: "direct main"
@ -327,14 +322,14 @@ packages:
name: flutter_local_notifications name: flutter_local_notifications
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "9.6.1" version: "9.9.1"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_linux name: flutter_local_notifications_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0+1" version: "0.5.1"
flutter_local_notifications_platform_interface: flutter_local_notifications_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -348,21 +343,21 @@ packages:
name: flutter_secure_storage name: flutter_secure_storage
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.0.2" version: "6.0.0"
flutter_secure_storage_linux: flutter_secure_storage_linux:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_linux name: flutter_secure_storage_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
flutter_secure_storage_macos: flutter_secure_storage_macos:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_macos name: flutter_secure_storage_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
flutter_secure_storage_platform_interface: flutter_secure_storage_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -397,7 +392,7 @@ packages:
name: flutter_slidable name: flutter_slidable
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.2"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -454,7 +449,7 @@ packages:
name: hive name: hive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -475,7 +470,7 @@ packages:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.13.4" version: "0.13.5"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -490,6 +485,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.1" version: "4.0.1"
hydrated_bloc:
dependency: "direct main"
description:
name: hydrated_bloc
url: "https://pub.dartlang.org"
source: hosted
version: "9.0.0-dev.3"
integration_test: integration_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -543,21 +545,21 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.11" version: "0.12.12"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.4" version: "0.1.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -613,7 +615,7 @@ packages:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -627,14 +629,14 @@ packages:
name: path_provider_android name: path_provider_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.16" version: "2.0.20"
path_provider_ios: path_provider_ios:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider_ios name: path_provider_ios
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.10" version: "2.0.11"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -662,7 +664,7 @@ packages:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.3"
pedantic: pedantic:
dependency: transitive dependency: transitive
description: description:
@ -748,28 +750,28 @@ packages:
name: responsive_builder name: responsive_builder
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.2" version: "0.4.3"
rxdart: rxdart:
dependency: "direct main" dependency: "direct main"
description: description:
name: rxdart name: rxdart
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.27.4" version: "0.27.5"
sembast: sembast:
dependency: "direct main" dependency: "direct main"
description: description:
name: sembast name: sembast
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.0+1" version: "3.3.0"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.10" version: "4.1.0"
share_plus_linux: share_plus_linux:
dependency: transitive dependency: transitive
description: description:
@ -818,7 +820,7 @@ packages:
name: shared_preferences_android name: shared_preferences_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.12" version: "2.0.13"
shared_preferences_ios: shared_preferences_ios:
dependency: "direct main" dependency: "direct main"
description: description:
@ -846,7 +848,7 @@ packages:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.1.0"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
@ -867,7 +869,7 @@ packages:
name: shelf name: shelf
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
shelf_packages_handler: shelf_packages_handler:
dependency: transitive dependency: transitive
description: description:
@ -921,14 +923,14 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.9.0"
sqflite: sqflite:
dependency: transitive dependency: transitive
description: description:
name: sqflite name: sqflite
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2+1" version: "2.0.3+1"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
@ -956,14 +958,14 @@ packages:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
sync_http: sync_http:
dependency: transitive dependency: transitive
description: description:
name: sync_http name: sync_http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0" version: "0.3.1"
synced_shared_preferences: synced_shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -977,35 +979,35 @@ packages:
name: synchronized name: synchronized
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0+2" version: "3.0.0+3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test: test:
dependency: transitive dependency: transitive
description: description:
name: test name: test
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.21.1" version: "1.21.4"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.9" version: "0.4.12"
test_core: test_core:
dependency: transitive dependency: transitive
description: description:
name: test_core name: test_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.13" version: "0.4.16"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:
@ -1026,7 +1028,7 @@ packages:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
universal_platform: universal_platform:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1040,14 +1042,14 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.4" version: "6.1.5"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.17" version: "6.0.19"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -1082,7 +1084,7 @@ packages:
name: url_launcher_web name: url_launcher_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.12" version: "2.0.13"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@ -1117,14 +1119,14 @@ packages:
name: vm_service name: vm_service
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.2.2" version: "9.0.0"
wakelock: wakelock:
dependency: "direct main" dependency: "direct main"
description: description:
name: wakelock name: wakelock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.1+2" version: "0.6.2"
wakelock_macos: wakelock_macos:
dependency: transitive dependency: transitive
description: description:
@ -1152,7 +1154,7 @@ packages:
name: wakelock_windows name: wakelock_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.2.1"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -1180,7 +1182,7 @@ packages:
name: webkit_inspection_protocol name: webkit_inspection_protocol
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
webview_flutter: webview_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1194,28 +1196,28 @@ packages:
name: webview_flutter_android name: webview_flutter_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.14" version: "2.10.1"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_platform_interface name: webview_flutter_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.1" version: "1.9.3"
webview_flutter_wkwebview: webview_flutter_wkwebview:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_wkwebview name: webview_flutter_wkwebview
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.1" version: "2.9.4"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.7.0" version: "3.0.0"
workmanager: workmanager:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1229,7 +1231,7 @@ packages:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0+1" version: "0.2.0+2"
xml: xml:
dependency: transitive dependency: transitive
description: description:
@ -1245,5 +1247,5 @@ packages:
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=2.17.0 <3.0.0" dart: ">=2.18.0 <3.0.0"
flutter: ">=3.0.3" flutter: ">=3.3.2"

View File

@ -1,22 +1,22 @@
name: hacki name: hacki
description: A Hacker News reader. description: A Hacker News reader.
version: 0.2.28+70 version: 0.2.31+74
publish_to: none publish_to: none
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"
flutter: "3.0.3" flutter: "3.3.2"
dependencies: dependencies:
adaptive_theme: ^3.0.0 adaptive_theme: ^3.0.0
badges: ^2.0.2 badges: ^2.0.2
bloc: ^8.0.3 bloc: ^8.1.0
cached_network_image: ^3.2.1 cached_network_image: ^3.2.1
clipboard: ^0.1.3 clipboard: ^0.1.3
collection: ^1.16.0 collection: ^1.16.0
connectivity_plus: ^2.2.1 connectivity_plus: ^2.3.7
dio: ^4.0.4 dio: ^4.0.4
equatable: 2.0.3 equatable: ^2.0.5
fast_gbk: ^1.0.0 fast_gbk: ^1.0.0
# feature_discovery: ^0.14.0 # feature_discovery: ^0.14.0
feature_discovery: feature_discovery:
@ -25,14 +25,18 @@ dependencies:
ref: flutter3_compatibility ref: flutter3_compatibility
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: ^8.0.1 flutter_bloc: ^8.1.1
flutter_cache_manager: ^3.3.0 flutter_cache_manager: ^3.3.0
flutter_fadein: ^2.0.0 flutter_fadein: ^2.0.0
flutter_feather_icons: 2.0.0+1 flutter_feather_icons: 2.0.0+1
flutter_inappwebview: ^5.4.3+4 # flutter_inappwebview: ^5.4.3+7
flutter_inappwebview:
git:
url: https://github.com/vocsyinfotech/flutter_inappwebview
ref: master
flutter_linkify: ^5.0.2 flutter_linkify: ^5.0.2
flutter_local_notifications: ^9.5.0 flutter_local_notifications: ^9.5.0
flutter_secure_storage: ^5.0.2 flutter_secure_storage: ^6.0.0
flutter_siri_suggestions: ^2.1.0 flutter_siri_suggestions: ^2.1.0
flutter_slidable: ^1.2.1 flutter_slidable: ^1.2.1
font_awesome_flutter: ^9.2.0 font_awesome_flutter: ^9.2.0
@ -42,6 +46,7 @@ dependencies:
html: ^0.15.0 html: ^0.15.0
html_unescape: ^2.0.0 html_unescape: ^2.0.0
http: ^0.13.3 http: ^0.13.3
hydrated_bloc: ^9.0.0-dev.3
intl: ^0.17.0 intl: ^0.17.0
logger: ^1.1.0 logger: ^1.1.0
path: ^1.8.0 path: ^1.8.0
@ -72,7 +77,7 @@ dependencies:
workmanager: ^0.5.0 workmanager: ^0.5.0
dev_dependencies: dev_dependencies:
bloc_test: ^9.0.3 bloc_test: ^9.1.0
flutter_test: flutter_test:
sdk: flutter sdk: flutter
integration_test: integration_test:

View File

@ -0,0 +1,29 @@
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (Map<String, dynamic>? data) async {
if (data != null) {
final driver.Timeline timeline = driver.Timeline.fromJson(
data['scrolling_timeline'] as Map<String, dynamic>,
);
// Convert the Timeline into a TimelineSummary that's easier to
// read and understand.
final driver.TimelineSummary summary =
driver.TimelineSummary.summarize(timeline);
// Then, write the entire timeline to disk in a json format.
// This file can be opened in the Chrome browser's tracing tools
// found by navigating to chrome://tracing.
// Optionally, save the summary to disk by setting includeSummary
// to true
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
);
}
},
);
}