Compare commits
28 Commits
v0.2.26
...
v0.2.32-rc
Author | SHA1 | Date | |
---|---|---|---|
b8ffa2ea2f | |||
caaa891700 | |||
e22dd18b91 | |||
0633c80147 | |||
ddf9b1457c | |||
e91293e625 | |||
f76a3109cc | |||
8373376b00 | |||
651923a4dd | |||
a6d8666c57 | |||
aa94d530d4 | |||
26f5b1ccca | |||
30dd0e137a | |||
3da53c692e | |||
94ca2deda4 | |||
47eaf00cd2 | |||
2b1e757f46 | |||
1e32fe5051 | |||
17686a9e1b | |||
a2c66a0075 | |||
0248792e66 | |||
5f43fd6968 | |||
d83381a7fd | |||
764ff09345 | |||
ab449adce2 | |||
2ec41b26f2 | |||
19f2107d95 | |||
c9b2d82dfc |
22
.github/workflows/commit_check.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Commit Guard
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
releases:
|
||||||
|
name: Check commit
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
FLUTTER_VERSION: "3.3.4"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: '3.3.4'
|
||||||
|
channel: 'stable'
|
||||||
|
- run: flutter pub get
|
||||||
|
- run: flutter format --set-exit-if-changed .
|
||||||
|
- run: flutter analyze
|
32
.github/workflows/github-actions.yml
vendored
@ -1,32 +0,0 @@
|
|||||||
name: Releases
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
# tags:
|
|
||||||
# - '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
releases:
|
|
||||||
name: release apk
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
JAVA_VERSION: "11.0"
|
|
||||||
FLUTTER_VERSION: "3.0.3"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-java@v2
|
|
||||||
with:
|
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
|
||||||
java-version: '17'
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
flutter-version: '3.0.3'
|
|
||||||
channel: 'stable'
|
|
||||||
- run: flutter pub get
|
|
||||||
- run: flutter analyze
|
|
||||||
# - run: flutter test
|
|
||||||
# - run: flutter build apk --release
|
|
||||||
# - uses: ncipollo/release-action@v1
|
|
||||||
# with:
|
|
||||||
# artifacts: "build/app/outputs/flutter-apk/*.apk"
|
|
||||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
|
54
.github/workflows/publish_ios.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
name: Publish (iOS)
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Allow manual builds of this workflow
|
||||||
|
workflow_dispatch: {}
|
||||||
|
# Run the workflow whenever a new tag named 'v*' is pushed
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "!*"
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_publish:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Point the `ruby/setup-ruby` action at this Gemfile, so it
|
||||||
|
# caches dependencies for us.
|
||||||
|
BUNDLE_GEMFILE: ${{ github.workspace }}/ios/Gemfile
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out from git
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
# Configure ruby according to our .ruby-version
|
||||||
|
- name: Setup ruby & Bundler
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
bundler-cache: true
|
||||||
|
# Set up flutter (feel free to adjust the version below)
|
||||||
|
- name: Setup flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
cache: true
|
||||||
|
flutter-version: 3.3.4
|
||||||
|
- run: flutter pub get
|
||||||
|
- run: flutter format --set-exit-if-changed .
|
||||||
|
- run: flutter analyze
|
||||||
|
# Start an ssh-agent that will provide the SSH key from the
|
||||||
|
# SSH_PRIVATE_KEY secret to `fastlane match`
|
||||||
|
- name: Setup SSH key
|
||||||
|
env:
|
||||||
|
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||||
|
run: |
|
||||||
|
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
|
||||||
|
ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}"
|
||||||
|
- name: Download dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
- name: Build & Publish to TestFlight with Fastlane
|
||||||
|
env:
|
||||||
|
APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}
|
||||||
|
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||||
|
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||||
|
run: cd ios && bundle exec fastlane beta "build_name:${{ github.ref_name }}"
|
1
.ruby-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
2.7.5
|
@ -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.
|
||||||
|
|
||||||
[](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone)
|
[](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone)
|
||||||
[](https://f-droid.org/en/packages/com.jiaqifeng.hacki/)
|
[](https://f-droid.org/en/packages/com.jiaqifeng.hacki/)
|
||||||
[](https://github.com/Livinglist/Hacki/releases/latest)
|
[](https://github.com/Livinglist/Hacki/releases/latest)
|
||||||
[](https://badges.pufler.dev)
|
|
||||||
[](https://img.shields.io/github/stars/livinglist/Hacki?style=social)
|
|
||||||
[](https://pub.dev/packages/effective_dart)
|
[](https://pub.dev/packages/effective_dart)
|
||||||
|
[](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/)
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 935 KiB After Width: | Height: | Size: 820 KiB |
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 406 KiB |
3
fastlane/metadata/android/en-US/changelogs/69.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
- Lazy loading.
|
||||||
|
- Offline mode now includes web pages.
|
||||||
|
- You can now sort comments in story screen.
|
3
fastlane/metadata/android/en-US/changelogs/70.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
- Lazy loading.
|
||||||
|
- Offline mode now includes web pages.
|
||||||
|
- You can now sort comments in story screen.
|
3
fastlane/metadata/android/en-US/changelogs/71.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
- Lazy loading.
|
||||||
|
- Offline mode now includes web pages.
|
||||||
|
- You can now sort comments in story screen.
|
3
fastlane/metadata/android/en-US/changelogs/72.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
- Lazy loading.
|
||||||
|
- Offline mode now includes web pages.
|
||||||
|
- You can now sort comments in story screen.
|
3
fastlane/metadata/android/en-US/changelogs/73.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
- Lazy loading.
|
||||||
|
- Offline mode now includes web pages.
|
||||||
|
- You can now sort comments in story screen.
|
2
fastlane/metadata/android/en-US/changelogs/74.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
- Bumped Flutter version.
|
||||||
|
- Updated navigation bar background color.
|
2
fastlane/metadata/android/en-US/changelogs/75.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
- Bumped Flutter version.
|
||||||
|
- Updated navigation bar background color.
|
Before Width: | Height: | Size: 935 KiB After Width: | Height: | Size: 820 KiB |
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 406 KiB |
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
68
integration_test/scrolling_test.dart
Normal 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',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -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>
|
||||||
|
4
ios/Gemfile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "fastlane"
|
||||||
|
gem "cocoapods"
|
285
ios/Gemfile.lock
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
CFPropertyList (3.0.5)
|
||||||
|
rexml
|
||||||
|
activesupport (6.1.7)
|
||||||
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
i18n (>= 1.6, < 2)
|
||||||
|
minitest (>= 5.1)
|
||||||
|
tzinfo (~> 2.0)
|
||||||
|
zeitwerk (~> 2.3)
|
||||||
|
addressable (2.8.1)
|
||||||
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
|
algoliasearch (1.27.5)
|
||||||
|
httpclient (~> 2.8, >= 2.8.3)
|
||||||
|
json (>= 1.5.1)
|
||||||
|
artifactory (3.0.15)
|
||||||
|
atomos (0.1.3)
|
||||||
|
aws-eventstream (1.2.0)
|
||||||
|
aws-partitions (1.636.0)
|
||||||
|
aws-sdk-core (3.154.0)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
aws-partitions (~> 1, >= 1.525.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
jmespath (~> 1, >= 1.6.1)
|
||||||
|
aws-sdk-kms (1.58.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sdk-s3 (1.114.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.4)
|
||||||
|
aws-sigv4 (1.5.1)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
babosa (1.0.4)
|
||||||
|
claide (1.1.0)
|
||||||
|
cocoapods (1.11.3)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
cocoapods-core (= 1.11.3)
|
||||||
|
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||||
|
cocoapods-downloader (>= 1.4.0, < 2.0)
|
||||||
|
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||||
|
cocoapods-search (>= 1.0.0, < 2.0)
|
||||||
|
cocoapods-trunk (>= 1.4.0, < 2.0)
|
||||||
|
cocoapods-try (>= 1.1.0, < 2.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
escape (~> 0.0.4)
|
||||||
|
fourflusher (>= 2.3.0, < 3.0)
|
||||||
|
gh_inspector (~> 1.0)
|
||||||
|
molinillo (~> 0.8.0)
|
||||||
|
nap (~> 1.0)
|
||||||
|
ruby-macho (>= 1.0, < 3.0)
|
||||||
|
xcodeproj (>= 1.21.0, < 2.0)
|
||||||
|
cocoapods-core (1.11.3)
|
||||||
|
activesupport (>= 5.0, < 7)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
algoliasearch (~> 1.0)
|
||||||
|
concurrent-ruby (~> 1.1)
|
||||||
|
fuzzy_match (~> 2.0.4)
|
||||||
|
nap (~> 1.0)
|
||||||
|
netrc (~> 0.11)
|
||||||
|
public_suffix (~> 4.0)
|
||||||
|
typhoeus (~> 1.0)
|
||||||
|
cocoapods-deintegrate (1.0.5)
|
||||||
|
cocoapods-downloader (1.6.3)
|
||||||
|
cocoapods-plugins (1.0.0)
|
||||||
|
nap
|
||||||
|
cocoapods-search (1.0.1)
|
||||||
|
cocoapods-trunk (1.6.0)
|
||||||
|
nap (>= 0.8, < 2.0)
|
||||||
|
netrc (~> 0.11)
|
||||||
|
cocoapods-try (1.2.0)
|
||||||
|
colored (1.2)
|
||||||
|
colored2 (3.1.2)
|
||||||
|
commander (4.6.0)
|
||||||
|
highline (~> 2.0.0)
|
||||||
|
concurrent-ruby (1.1.10)
|
||||||
|
declarative (0.0.20)
|
||||||
|
digest-crc (0.6.4)
|
||||||
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
|
domain_name (0.5.20190701)
|
||||||
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
|
dotenv (2.8.1)
|
||||||
|
emoji_regex (3.2.3)
|
||||||
|
escape (0.0.4)
|
||||||
|
ethon (0.15.0)
|
||||||
|
ffi (>= 1.15.0)
|
||||||
|
excon (0.92.5)
|
||||||
|
faraday (1.10.2)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
ruby2_keywords (>= 0.0.4)
|
||||||
|
faraday-cookie_jar (0.0.7)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
|
http-cookie (~> 1.0.0)
|
||||||
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.0)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-multipart (1.0.4)
|
||||||
|
multipart-post (~> 2)
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.3)
|
||||||
|
faraday_middleware (1.2.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
fastimage (2.2.6)
|
||||||
|
fastlane (2.210.1)
|
||||||
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
|
addressable (>= 2.8, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
|
aws-sdk-s3 (~> 1.0)
|
||||||
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
|
colored
|
||||||
|
commander (~> 4.6)
|
||||||
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
|
faraday_middleware (~> 1.0)
|
||||||
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
|
json (< 3.0.0)
|
||||||
|
jwt (>= 2.1.0, < 3)
|
||||||
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
|
multipart-post (~> 2.0.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
optparse (~> 0.1.1)
|
||||||
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
|
security (= 0.1.3)
|
||||||
|
simctl (~> 1.6.3)
|
||||||
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
|
terminal-table (>= 1.4.5, < 2.0.0)
|
||||||
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
|
word_wrap (~> 1.0.0)
|
||||||
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
|
xcpretty (~> 0.3.0)
|
||||||
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
|
ffi (1.15.5)
|
||||||
|
fourflusher (2.3.1)
|
||||||
|
fuzzy_match (2.0.4)
|
||||||
|
gh_inspector (1.1.3)
|
||||||
|
google-apis-androidpublisher_v3 (0.27.0)
|
||||||
|
google-apis-core (>= 0.7.2, < 2.a)
|
||||||
|
google-apis-core (0.9.0)
|
||||||
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
representable (~> 3.0)
|
||||||
|
retriable (>= 2.0, < 4.a)
|
||||||
|
rexml
|
||||||
|
webrick
|
||||||
|
google-apis-iamcredentials_v1 (0.14.0)
|
||||||
|
google-apis-core (>= 0.7.2, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.10.0)
|
||||||
|
google-apis-core (>= 0.7, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.17.0)
|
||||||
|
google-apis-core (>= 0.7, < 2.a)
|
||||||
|
google-cloud-core (1.6.0)
|
||||||
|
google-cloud-env (~> 1.0)
|
||||||
|
google-cloud-errors (~> 1.0)
|
||||||
|
google-cloud-env (1.6.0)
|
||||||
|
faraday (>= 0.17.3, < 3.0)
|
||||||
|
google-cloud-errors (1.3.0)
|
||||||
|
google-cloud-storage (1.42.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
digest-crc (~> 0.4)
|
||||||
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
|
google-apis-storage_v1 (~> 0.17.0)
|
||||||
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
googleauth (1.2.0)
|
||||||
|
faraday (>= 0.17.3, < 3.a)
|
||||||
|
jwt (>= 1.4, < 3.0)
|
||||||
|
memoist (~> 0.16)
|
||||||
|
multi_json (~> 1.11)
|
||||||
|
os (>= 0.9, < 2.0)
|
||||||
|
signet (>= 0.16, < 2.a)
|
||||||
|
highline (2.0.3)
|
||||||
|
http-cookie (1.0.5)
|
||||||
|
domain_name (~> 0.5)
|
||||||
|
httpclient (2.8.3)
|
||||||
|
i18n (1.12.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
jmespath (1.6.1)
|
||||||
|
json (2.6.2)
|
||||||
|
jwt (2.5.0)
|
||||||
|
memoist (0.16.2)
|
||||||
|
mini_magick (4.11.0)
|
||||||
|
mini_mime (1.1.2)
|
||||||
|
minitest (5.16.3)
|
||||||
|
molinillo (0.8.0)
|
||||||
|
multi_json (1.15.0)
|
||||||
|
multipart-post (2.0.0)
|
||||||
|
nanaimo (0.3.0)
|
||||||
|
nap (1.1.0)
|
||||||
|
naturally (2.2.1)
|
||||||
|
netrc (0.11.0)
|
||||||
|
optparse (0.1.1)
|
||||||
|
os (1.1.4)
|
||||||
|
plist (3.6.0)
|
||||||
|
public_suffix (4.0.7)
|
||||||
|
rake (13.0.6)
|
||||||
|
representable (3.2.0)
|
||||||
|
declarative (< 0.1.0)
|
||||||
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
|
uber (< 0.2.0)
|
||||||
|
retriable (3.1.2)
|
||||||
|
rexml (3.2.5)
|
||||||
|
rouge (2.0.7)
|
||||||
|
ruby-macho (2.5.1)
|
||||||
|
ruby2_keywords (0.0.5)
|
||||||
|
rubyzip (2.3.2)
|
||||||
|
security (0.1.3)
|
||||||
|
signet (0.17.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
faraday (>= 0.17.5, < 3.a)
|
||||||
|
jwt (>= 1.5, < 3.0)
|
||||||
|
multi_json (~> 1.10)
|
||||||
|
simctl (1.6.8)
|
||||||
|
CFPropertyList
|
||||||
|
naturally
|
||||||
|
terminal-notifier (2.0.0)
|
||||||
|
terminal-table (1.8.0)
|
||||||
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
|
trailblazer-option (0.1.2)
|
||||||
|
tty-cursor (0.7.1)
|
||||||
|
tty-screen (0.8.1)
|
||||||
|
tty-spinner (0.9.3)
|
||||||
|
tty-cursor (~> 0.7)
|
||||||
|
typhoeus (1.4.0)
|
||||||
|
ethon (>= 0.9.0)
|
||||||
|
tzinfo (2.0.5)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
uber (0.1.0)
|
||||||
|
unf (0.1.4)
|
||||||
|
unf_ext
|
||||||
|
unf_ext (0.0.8.2)
|
||||||
|
unicode-display_width (1.8.0)
|
||||||
|
webrick (1.7.0)
|
||||||
|
word_wrap (1.0.0)
|
||||||
|
xcodeproj (1.22.0)
|
||||||
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
|
atomos (~> 0.1.3)
|
||||||
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
nanaimo (~> 0.3.0)
|
||||||
|
rexml (~> 3.2.4)
|
||||||
|
xcpretty (0.3.0)
|
||||||
|
rouge (~> 2.0.7)
|
||||||
|
xcpretty-travis-formatter (1.0.1)
|
||||||
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
zeitwerk (2.6.0)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
universal-darwin-21
|
||||||
|
x86_64-darwin-19
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
cocoapods
|
||||||
|
fastlane
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.3.22
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
@ -567,20 +567,23 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
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_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CODE_SIGN_STYLE = Manual;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 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.26;
|
MARKETING_VERSION = 0.2.32;
|
||||||
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 = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.jiaqi.hacki";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@ -635,7 +638,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 +687,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;
|
||||||
@ -704,20 +707,23 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
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_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CODE_SIGN_STYLE = Manual;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 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.26;
|
MARKETING_VERSION = 0.2.32;
|
||||||
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 = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.jiaqi.hacki";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -735,20 +741,23 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
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_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CODE_SIGN_STYLE = Manual;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 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.26;
|
MARKETING_VERSION = 0.2.32;
|
||||||
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 = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.jiaqi.hacki";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@ -767,9 +776,11 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Share Extension/Info.plist";
|
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||||
@ -786,6 +797,8 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Share-Extension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Share-Extension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "hacki share extension profile";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@ -806,9 +819,11 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Share Extension/Info.plist";
|
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||||
@ -824,6 +839,8 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Share-Extension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Share-Extension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.jiaqi.hacki.Share-Extension";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -842,9 +859,11 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Share Extension/Info.plist";
|
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||||
@ -860,6 +879,8 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Share-Extension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Share-Extension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "hacki share extension profile";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -880,9 +901,11 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Action Extension/Action Extension.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Action Extension/Action Extension.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Action Extension/Info.plist";
|
INFOPLIST_FILE = "Action Extension/Info.plist";
|
||||||
@ -899,6 +922,8 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Action-Extension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Action-Extension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "hacki action extension profile";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@ -921,9 +946,11 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Action Extension/Action Extension.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Action Extension/Action Extension.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Action Extension/Info.plist";
|
INFOPLIST_FILE = "Action Extension/Info.plist";
|
||||||
@ -939,6 +966,8 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Action-Extension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Action-Extension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.jiaqi.hacki.Action-Extension";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -959,9 +988,11 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Action Extension/Action Extension.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Action Extension/Action Extension.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Action Extension/Info.plist";
|
INFOPLIST_FILE = "Action Extension/Info.plist";
|
||||||
@ -977,6 +1008,8 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Action-Extension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.jiaqi.hacki.Action-Extension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "hacki action extension profile";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
8
ios/fastlane/Appfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
app_identifier("com.jiaqi.hacki") # The bundle identifier of your app
|
||||||
|
apple_id("georgefung78@Live.com") # Your Apple Developer Portal username
|
||||||
|
|
||||||
|
itc_team_id("120097292") # App Store Connect Team ID
|
||||||
|
team_id("QMWX3X2NF7") # Developer Portal Team ID
|
||||||
|
|
||||||
|
# For more information about the Appfile, see:
|
||||||
|
# https://docs.fastlane.tools/advanced/#appfile
|
96
ios/fastlane/Fastfile
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# This file contains the fastlane.tools configuration
|
||||||
|
# You can find the documentation at https://docs.fastlane.tools
|
||||||
|
#
|
||||||
|
# For a list of all available actions, check out
|
||||||
|
#
|
||||||
|
# https://docs.fastlane.tools/actions
|
||||||
|
#
|
||||||
|
# For a list of all available plugins, check out
|
||||||
|
#
|
||||||
|
# https://docs.fastlane.tools/plugins/available-plugins
|
||||||
|
#
|
||||||
|
|
||||||
|
## Update these lines to match your project!
|
||||||
|
# Bundle Identifier used for the iOS App on the App Store Connect portal
|
||||||
|
APP_IDENTIFIER = 'com.jiaqi.hacki'
|
||||||
|
# Issuer ID from the Keys section of https://appstoreconnect.apple.com/access/users
|
||||||
|
APPSTORECONNECT_ISSUER_ID = '0b588ac9-5b3e-4420-867a-a33decce7b91'
|
||||||
|
# Key ID from the key matching the `APP_STORE_CONNECT_API_KEY_KEY` secret, found under the Keys section of https://appstoreconnect.apple.com/access/users
|
||||||
|
APPSTORECONNECT_KEY_ID = 'DPNP8R66QS'
|
||||||
|
|
||||||
|
# Uncomment the line if you want fastlane to automatically update itself
|
||||||
|
# update_fastlane
|
||||||
|
|
||||||
|
default_platform(:ios)
|
||||||
|
|
||||||
|
platform :ios do
|
||||||
|
desc "Push a new beta build to TestFlight"
|
||||||
|
|
||||||
|
lane :beta do |options|
|
||||||
|
setup_ci if ENV['CI']
|
||||||
|
|
||||||
|
is_example_repo = ENV['CI'] && ENV['GITHUB_REPOSITORY'] == 'jorgenpt/flutter_github_example'
|
||||||
|
|
||||||
|
if !is_example_repo && APP_IDENTIFIER == 'no.tjer.HelloWorld' then
|
||||||
|
UI.user_error! "You need to update your Fastfile to use your own `APP_IDENTIFIER`"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Download code signing certificates using `match` (and the `MATCH_PASSWORD` secret)
|
||||||
|
sync_code_signing(
|
||||||
|
type: "appstore",
|
||||||
|
app_identifier: [APP_IDENTIFIER, "#{APP_IDENTIFIER}.Share-Extension", "#{APP_IDENTIFIER}.Action-Extension"],
|
||||||
|
readonly: true
|
||||||
|
)
|
||||||
|
|
||||||
|
if !is_example_repo then
|
||||||
|
if APPSTORECONNECT_ISSUER_ID == '69a6de83-feb7-47e3-e053-5b8c7c11a4d1' then
|
||||||
|
UI.user_error! "You need to update your Fastfile to use your own `APPSTORECONNECT_ISSUER_ID`"
|
||||||
|
end
|
||||||
|
if APPSTORECONNECT_KEY_ID == 'YRQDJRKMR9' then
|
||||||
|
UI.user_error! "You need to update your Fastfile to use your own `APPSTORECONNECT_KEY_ID`"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# We expose the key data using `APP_STORE_CONNECT_API_KEY_KEY` secret on GH
|
||||||
|
app_store_connect_api_key(
|
||||||
|
key_id: APPSTORECONNECT_KEY_ID,
|
||||||
|
issuer_id: APPSTORECONNECT_ISSUER_ID
|
||||||
|
)
|
||||||
|
latest_testflight_build_number
|
||||||
|
# Figure out the build number (and optionally build name)
|
||||||
|
new_build_number = ( + 1)
|
||||||
|
extra_config_args = []
|
||||||
|
if options.key?(:build_name) then
|
||||||
|
extra_config_args = ["--build-name", options[:build_name].delete_prefix('v')]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prep the xcodeproject from Flutter without building (`--config-only`)
|
||||||
|
sh(
|
||||||
|
"flutter", "build", "ios", "--config-only",
|
||||||
|
"--release", "--no-pub", "--no-codesign",
|
||||||
|
"--build-number", new_build_number.to_s,
|
||||||
|
*extra_config_args
|
||||||
|
)
|
||||||
|
|
||||||
|
increment_version_number(
|
||||||
|
version_number: options[:build_name].delete_prefix('v').delete_suffix('-rc')
|
||||||
|
)
|
||||||
|
|
||||||
|
increment_build_number({
|
||||||
|
build_number: latest_testflight_build_number + 1
|
||||||
|
})
|
||||||
|
|
||||||
|
# Build & sign using Runner.xcworkspace
|
||||||
|
build_app(
|
||||||
|
workspace: "Runner.xcworkspace",
|
||||||
|
scheme: "Runner",
|
||||||
|
output_directory: "../build/ios/archive"
|
||||||
|
)
|
||||||
|
|
||||||
|
upload_to_testflight(
|
||||||
|
# This takes a long time, so don't waste GH runner minutes (but it means manually needing to
|
||||||
|
# set the build live for external testers).
|
||||||
|
skip_waiting_for_build_processing: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
13
ios/fastlane/Matchfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
git_url("git@github.com:Livinglist/certificates.git")
|
||||||
|
|
||||||
|
storage_mode("git")
|
||||||
|
|
||||||
|
type("development") # The default type, can be: appstore, adhoc, enterprise or development
|
||||||
|
|
||||||
|
# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])
|
||||||
|
# username("user@fastlane.tools") # Your Apple Developer Portal username
|
||||||
|
|
||||||
|
# For all available options run `fastlane match --help`
|
||||||
|
# Remove the # in the beginning of the line to enable the other options
|
||||||
|
|
||||||
|
# The docs are available on https://docs.fastlane.tools/actions/match
|
@ -2,8 +2,7 @@ import 'package:logger/logger.dart';
|
|||||||
|
|
||||||
class CustomLogFilter extends LogFilter {
|
class CustomLogFilter extends LogFilter {
|
||||||
@override
|
@override
|
||||||
// ignore: overridden_fields
|
Level? get level => Level.verbose;
|
||||||
Level? level = Level.verbose;
|
|
||||||
|
|
||||||
/// The minimal level allowed in production.
|
/// The minimal level allowed in production.
|
||||||
static const Level _minimalLevel = Level.info;
|
static const Level _minimalLevel = Level.info;
|
||||||
|
@ -11,6 +11,7 @@ import 'package:hacki/models/models.dart';
|
|||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
import 'package:hacki/screens/screens.dart';
|
import 'package:hacki/screens/screens.dart';
|
||||||
import 'package:hacki/services/services.dart';
|
import 'package:hacki/services/services.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
part 'comments_state.dart';
|
part 'comments_state.dart';
|
||||||
|
|
||||||
@ -21,8 +22,11 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
OfflineRepository? offlineRepository,
|
OfflineRepository? offlineRepository,
|
||||||
StoriesRepository? storiesRepository,
|
StoriesRepository? storiesRepository,
|
||||||
SembastRepository? sembastRepository,
|
SembastRepository? sembastRepository,
|
||||||
|
Logger? logger,
|
||||||
required bool offlineReading,
|
required bool offlineReading,
|
||||||
required Item item,
|
required Item item,
|
||||||
|
required FetchMode defaultFetchMode,
|
||||||
|
required CommentsOrder defaultCommentsOrder,
|
||||||
}) : _collapseCache = collapseCache,
|
}) : _collapseCache = collapseCache,
|
||||||
_commentCache = commentCache ?? locator.get<CommentCache>(),
|
_commentCache = commentCache ?? locator.get<CommentCache>(),
|
||||||
_offlineRepository =
|
_offlineRepository =
|
||||||
@ -31,15 +35,32 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
storiesRepository ?? locator.get<StoriesRepository>(),
|
storiesRepository ?? locator.get<StoriesRepository>(),
|
||||||
_sembastRepository =
|
_sembastRepository =
|
||||||
sembastRepository ?? locator.get<SembastRepository>(),
|
sembastRepository ?? locator.get<SembastRepository>(),
|
||||||
super(CommentsState.init(offlineReading: offlineReading, item: item));
|
_logger = logger ?? locator.get<Logger>(),
|
||||||
|
super(
|
||||||
|
CommentsState.init(
|
||||||
|
offlineReading: offlineReading,
|
||||||
|
item: item,
|
||||||
|
fetchMode: defaultFetchMode,
|
||||||
|
order: defaultCommentsOrder,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final CollapseCache _collapseCache;
|
final CollapseCache _collapseCache;
|
||||||
final CommentCache _commentCache;
|
final CommentCache _commentCache;
|
||||||
final OfflineRepository _offlineRepository;
|
final OfflineRepository _offlineRepository;
|
||||||
final StoriesRepository _storiesRepository;
|
final StoriesRepository _storiesRepository;
|
||||||
final SembastRepository _sembastRepository;
|
final SembastRepository _sembastRepository;
|
||||||
|
final Logger _logger;
|
||||||
|
|
||||||
|
/// The [StreamSubscription] for stream (both lazy or eager)
|
||||||
|
/// fetching comments posted directly to the story.
|
||||||
StreamSubscription<Comment>? _streamSubscription;
|
StreamSubscription<Comment>? _streamSubscription;
|
||||||
|
|
||||||
|
/// The map of [StreamSubscription] for streams
|
||||||
|
/// fetching comments lazily. [int] is the id of parent comment.
|
||||||
|
final Map<int, StreamSubscription<Comment>> _streamSubscriptions =
|
||||||
|
<int, StreamSubscription<Comment>>{};
|
||||||
|
|
||||||
static const int _pageSize = 20;
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -64,7 +85,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
_streamSubscription = _storiesRepository
|
_streamSubscription = _storiesRepository
|
||||||
.fetchCommentsStream(
|
.fetchAllCommentsRecursivelyStream(
|
||||||
ids: targetParents!.last.kids,
|
ids: targetParents!.last.kids,
|
||||||
level: targetParents.last.level + 1,
|
level: targetParents.last.level + 1,
|
||||||
)
|
)
|
||||||
@ -74,7 +95,13 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(status: CommentsStatus.loading));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: CommentsStatus.loading,
|
||||||
|
comments: <Comment>[],
|
||||||
|
currentPage: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final Item item = state.item;
|
final Item item = state.item;
|
||||||
final Item updatedItem = state.offlineReading
|
final Item updatedItem = state.offlineReading
|
||||||
@ -90,6 +117,8 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
.listen(_onCommentFetched)
|
.listen(_onCommentFetched)
|
||||||
..onDone(_onDone);
|
..onDone(_onDone);
|
||||||
} else {
|
} else {
|
||||||
|
switch (state.fetchMode) {
|
||||||
|
case FetchMode.lazy:
|
||||||
_streamSubscription = _storiesRepository
|
_streamSubscription = _storiesRepository
|
||||||
.fetchCommentsStream(
|
.fetchCommentsStream(
|
||||||
ids: kids,
|
ids: kids,
|
||||||
@ -97,6 +126,17 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
)
|
)
|
||||||
.listen(_onCommentFetched)
|
.listen(_onCommentFetched)
|
||||||
..onDone(_onDone);
|
..onDone(_onDone);
|
||||||
|
break;
|
||||||
|
case FetchMode.eager:
|
||||||
|
_streamSubscription = _storiesRepository
|
||||||
|
.fetchAllCommentsRecursivelyStream(
|
||||||
|
ids: kids,
|
||||||
|
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
||||||
|
)
|
||||||
|
.listen(_onCommentFetched)
|
||||||
|
..onDone(_onDone);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,26 +150,47 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_collapseCache.resetCollapsedComments();
|
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: CommentsStatus.loading,
|
status: CommentsStatus.loading,
|
||||||
comments: <Comment>[],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_collapseCache.resetCollapsedComments();
|
||||||
|
|
||||||
await _streamSubscription?.cancel();
|
await _streamSubscription?.cancel();
|
||||||
|
for (final int id in _streamSubscriptions.keys) {
|
||||||
|
await _streamSubscriptions[id]?.cancel();
|
||||||
|
}
|
||||||
|
_streamSubscriptions.clear();
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
comments: <Comment>[],
|
||||||
|
currentPage: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final Item item = state.item;
|
final Item item = state.item;
|
||||||
final Item updatedItem =
|
final Item updatedItem =
|
||||||
await _storiesRepository.fetchItemBy(id: item.id) ?? item;
|
await _storiesRepository.fetchItemBy(id: item.id) ?? item;
|
||||||
final List<int> kids = sortKids(updatedItem.kids);
|
final List<int> kids = sortKids(updatedItem.kids);
|
||||||
|
|
||||||
|
if (state.fetchMode == FetchMode.lazy) {
|
||||||
_streamSubscription = _storiesRepository
|
_streamSubscription = _storiesRepository
|
||||||
.fetchCommentsStream(ids: kids)
|
.fetchCommentsStream(
|
||||||
|
ids: kids,
|
||||||
|
)
|
||||||
.listen(_onCommentFetched)
|
.listen(_onCommentFetched)
|
||||||
..onDone(_onDone);
|
..onDone(_onDone);
|
||||||
|
} else {
|
||||||
|
_streamSubscription = _storiesRepository
|
||||||
|
.fetchAllCommentsRecursivelyStream(
|
||||||
|
ids: kids,
|
||||||
|
)
|
||||||
|
.listen(_onCommentFetched)
|
||||||
|
..onDone(_onDone);
|
||||||
|
}
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -144,18 +205,68 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
onlyShowTargetComment: false,
|
onlyShowTargetComment: false,
|
||||||
comments: <Comment>[],
|
|
||||||
item: story,
|
item: story,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadMore() {
|
/// [comment] is only used for lazy fetching.
|
||||||
|
void loadMore({Comment? comment}) {
|
||||||
|
switch (state.fetchMode) {
|
||||||
|
case FetchMode.lazy:
|
||||||
|
if (comment == null) return;
|
||||||
|
if (_streamSubscriptions.containsKey(comment.id)) return;
|
||||||
|
|
||||||
|
final int level = comment.level + 1;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
/// Ignoring because the subscription will be cancelled in close()
|
||||||
|
// ignore: cancel_subscriptions
|
||||||
|
final StreamSubscription<Comment> streamSubscription =
|
||||||
|
_storiesRepository
|
||||||
|
.fetchCommentsStream(ids: comment.kids)
|
||||||
|
.listen((Comment cmt) {
|
||||||
|
_collapseCache.addKid(cmt.id, to: cmt.parent);
|
||||||
|
_commentCache.cacheComment(cmt);
|
||||||
|
_sembastRepository.cacheComment(cmt);
|
||||||
|
|
||||||
|
final List<LinkifyElement> elements = _linkify(
|
||||||
|
cmt.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
final BuildableComment buildableComment =
|
||||||
|
BuildableComment.fromComment(cmt, elements: elements);
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
comments: <Comment>[...state.comments]..insert(
|
||||||
|
state.comments.indexOf(comment) + offset + 1,
|
||||||
|
buildableComment.copyWith(level: level),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
offset++;
|
||||||
|
})
|
||||||
|
..onDone(() {
|
||||||
|
_streamSubscriptions[comment.id]?.cancel();
|
||||||
|
_streamSubscriptions.remove(comment.id);
|
||||||
|
})
|
||||||
|
..onError((dynamic error) {
|
||||||
|
_logger.e(error);
|
||||||
|
_streamSubscriptions[comment.id]?.cancel();
|
||||||
|
_streamSubscriptions.remove(comment.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
_streamSubscriptions[comment.id] = streamSubscription;
|
||||||
|
break;
|
||||||
|
case FetchMode.eager:
|
||||||
if (_streamSubscription != null) {
|
if (_streamSubscription != null) {
|
||||||
emit(state.copyWith(status: CommentsStatus.loading));
|
emit(state.copyWith(status: CommentsStatus.loading));
|
||||||
_streamSubscription?.resume();
|
_streamSubscription?.resume();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadParentThread() async {
|
Future<void> loadParentThread() async {
|
||||||
@ -181,10 +292,29 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onOrderChanged(CommentsOrder? order) {
|
void onOrderChanged(CommentsOrder? order) {
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
if (order == null) return;
|
if (order == null) return;
|
||||||
|
if (state.order == order) return;
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
_streamSubscription?.cancel();
|
_streamSubscription?.cancel();
|
||||||
emit(state.copyWith(order: order, comments: <Comment>[]));
|
for (final StreamSubscription<Comment> s in _streamSubscriptions.values) {
|
||||||
|
s.cancel();
|
||||||
|
}
|
||||||
|
_streamSubscriptions.clear();
|
||||||
|
emit(state.copyWith(order: order));
|
||||||
|
init(useCommentCache: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFetchModeChanged(FetchMode? fetchMode) {
|
||||||
|
if (fetchMode == null) return;
|
||||||
|
if (state.fetchMode == fetchMode) return;
|
||||||
|
_collapseCache.resetCollapsedComments();
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
_streamSubscription?.cancel();
|
||||||
|
for (final StreamSubscription<Comment> s in _streamSubscriptions.values) {
|
||||||
|
s.cancel();
|
||||||
|
}
|
||||||
|
_streamSubscriptions.clear();
|
||||||
|
emit(state.copyWith(fetchMode: fetchMode));
|
||||||
init(useCommentCache: true);
|
init(useCommentCache: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,7 +359,9 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
|
|
||||||
emit(state.copyWith(comments: updatedComments));
|
emit(state.copyWith(comments: updatedComments));
|
||||||
|
|
||||||
if (updatedComments.length >= _pageSize + _pageSize * state.currentPage &&
|
if (state.fetchMode == FetchMode.eager) {
|
||||||
|
if (updatedComments.length >=
|
||||||
|
_pageSize + _pageSize * state.currentPage &&
|
||||||
updatedComments.length <=
|
updatedComments.length <=
|
||||||
_pageSize * 2 + _pageSize * state.currentPage) {
|
_pageSize * 2 + _pageSize * state.currentPage) {
|
||||||
final bool isHidden = _collapseCache.isHidden(comment.id);
|
final bool isHidden = _collapseCache.isHidden(comment.id);
|
||||||
@ -247,6 +379,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static List<LinkifyElement> _linkify(
|
static List<LinkifyElement> _linkify(
|
||||||
String text, {
|
String text, {
|
||||||
@ -276,6 +409,9 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _streamSubscription?.cancel();
|
await _streamSubscription?.cancel();
|
||||||
|
for (final StreamSubscription<Comment> s in _streamSubscriptions.values) {
|
||||||
|
await s.cancel();
|
||||||
|
}
|
||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,11 @@ enum CommentsOrder {
|
|||||||
oldestFirst,
|
oldestFirst,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FetchMode {
|
||||||
|
lazy,
|
||||||
|
eager,
|
||||||
|
}
|
||||||
|
|
||||||
class CommentsState extends Equatable {
|
class CommentsState extends Equatable {
|
||||||
const CommentsState({
|
const CommentsState({
|
||||||
required this.item,
|
required this.item,
|
||||||
@ -21,6 +26,7 @@ class CommentsState extends Equatable {
|
|||||||
required this.status,
|
required this.status,
|
||||||
required this.fetchParentStatus,
|
required this.fetchParentStatus,
|
||||||
required this.order,
|
required this.order,
|
||||||
|
required this.fetchMode,
|
||||||
required this.onlyShowTargetComment,
|
required this.onlyShowTargetComment,
|
||||||
required this.offlineReading,
|
required this.offlineReading,
|
||||||
required this.currentPage,
|
required this.currentPage,
|
||||||
@ -29,10 +35,11 @@ class CommentsState extends Equatable {
|
|||||||
CommentsState.init({
|
CommentsState.init({
|
||||||
required this.offlineReading,
|
required this.offlineReading,
|
||||||
required this.item,
|
required this.item,
|
||||||
|
required this.fetchMode,
|
||||||
|
required this.order,
|
||||||
}) : comments = <Comment>[],
|
}) : comments = <Comment>[],
|
||||||
status = CommentsStatus.init,
|
status = CommentsStatus.init,
|
||||||
fetchParentStatus = CommentsStatus.init,
|
fetchParentStatus = CommentsStatus.init,
|
||||||
order = CommentsOrder.natural,
|
|
||||||
onlyShowTargetComment = false,
|
onlyShowTargetComment = false,
|
||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
|
|
||||||
@ -41,6 +48,7 @@ class CommentsState extends Equatable {
|
|||||||
final CommentsStatus status;
|
final CommentsStatus status;
|
||||||
final CommentsStatus fetchParentStatus;
|
final CommentsStatus fetchParentStatus;
|
||||||
final CommentsOrder order;
|
final CommentsOrder order;
|
||||||
|
final FetchMode fetchMode;
|
||||||
final bool onlyShowTargetComment;
|
final bool onlyShowTargetComment;
|
||||||
final bool offlineReading;
|
final bool offlineReading;
|
||||||
final int currentPage;
|
final int currentPage;
|
||||||
@ -51,6 +59,7 @@ class CommentsState extends Equatable {
|
|||||||
CommentsStatus? status,
|
CommentsStatus? status,
|
||||||
CommentsStatus? fetchParentStatus,
|
CommentsStatus? fetchParentStatus,
|
||||||
CommentsOrder? order,
|
CommentsOrder? order,
|
||||||
|
FetchMode? fetchMode,
|
||||||
bool? onlyShowTargetComment,
|
bool? onlyShowTargetComment,
|
||||||
bool? offlineReading,
|
bool? offlineReading,
|
||||||
int? currentPage,
|
int? currentPage,
|
||||||
@ -61,6 +70,7 @@ class CommentsState extends Equatable {
|
|||||||
fetchParentStatus: fetchParentStatus ?? this.fetchParentStatus,
|
fetchParentStatus: fetchParentStatus ?? this.fetchParentStatus,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
order: order ?? this.order,
|
order: order ?? this.order,
|
||||||
|
fetchMode: fetchMode ?? this.fetchMode,
|
||||||
onlyShowTargetComment:
|
onlyShowTargetComment:
|
||||||
onlyShowTargetComment ?? this.onlyShowTargetComment,
|
onlyShowTargetComment ?? this.onlyShowTargetComment,
|
||||||
offlineReading: offlineReading ?? this.offlineReading,
|
offlineReading: offlineReading ?? this.offlineReading,
|
||||||
@ -68,6 +78,8 @@ class CommentsState extends Equatable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<int> get commentIds => comments.map((Comment e) => e.id).toSet();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[
|
List<Object?> get props => <Object?>[
|
||||||
item,
|
item,
|
||||||
@ -75,6 +87,7 @@ class CommentsState extends Equatable {
|
|||||||
status,
|
status,
|
||||||
fetchParentStatus,
|
fetchParentStatus,
|
||||||
order,
|
order,
|
||||||
|
fetchMode,
|
||||||
onlyShowTargetComment,
|
onlyShowTargetComment,
|
||||||
offlineReading,
|
offlineReading,
|
||||||
currentPage,
|
currentPage,
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
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/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
|
|
||||||
part 'preference_state.dart';
|
part 'preference_state.dart';
|
||||||
@ -33,6 +35,10 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
|||||||
.then((bool value) => emit(state.copyWith(markReadStories: value)));
|
.then((bool value) => emit(state.copyWith(markReadStories: value)));
|
||||||
_preferenceRepository.shouldShowMetadata
|
_preferenceRepository.shouldShowMetadata
|
||||||
.then((bool value) => emit(state.copyWith(showMetadata: value)));
|
.then((bool value) => emit(state.copyWith(showMetadata: value)));
|
||||||
|
_preferenceRepository.fetchMode
|
||||||
|
.then((FetchMode value) => emit(state.copyWith(fetchMode: value)));
|
||||||
|
_preferenceRepository.commentsOrder
|
||||||
|
.then((CommentsOrder value) => emit(state.copyWith(order: value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleNotificationMode() {
|
void toggleNotificationMode() {
|
||||||
@ -74,4 +80,18 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
|||||||
emit(state.copyWith(showMetadata: !state.showMetadata));
|
emit(state.copyWith(showMetadata: !state.showMetadata));
|
||||||
_preferenceRepository.toggleMetadataMode();
|
_preferenceRepository.toggleMetadataMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void selectFetchMode(FetchMode? fetchMode) {
|
||||||
|
if (fetchMode == null || state.fetchMode == fetchMode) return;
|
||||||
|
HapticFeedback.lightImpact();
|
||||||
|
emit(state.copyWith(fetchMode: fetchMode));
|
||||||
|
_preferenceRepository.selectFetchMode(fetchMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectCommentsOrder(CommentsOrder? order) {
|
||||||
|
if (order == null || state.order == order) return;
|
||||||
|
HapticFeedback.lightImpact();
|
||||||
|
emit(state.copyWith(order: order));
|
||||||
|
_preferenceRepository.selectCommentsOrder(order);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ class PreferenceState extends Equatable {
|
|||||||
required this.useReader,
|
required this.useReader,
|
||||||
required this.markReadStories,
|
required this.markReadStories,
|
||||||
required this.showMetadata,
|
required this.showMetadata,
|
||||||
|
required this.fetchMode,
|
||||||
|
required this.order,
|
||||||
});
|
});
|
||||||
|
|
||||||
const PreferenceState.init()
|
const PreferenceState.init()
|
||||||
@ -20,7 +22,9 @@ class PreferenceState extends Equatable {
|
|||||||
useTrueDark = false,
|
useTrueDark = false,
|
||||||
useReader = false,
|
useReader = false,
|
||||||
markReadStories = false,
|
markReadStories = false,
|
||||||
showMetadata = false;
|
showMetadata = false,
|
||||||
|
fetchMode = FetchMode.eager,
|
||||||
|
order = CommentsOrder.natural;
|
||||||
|
|
||||||
final bool showNotification;
|
final bool showNotification;
|
||||||
final bool showComplexStoryTile;
|
final bool showComplexStoryTile;
|
||||||
@ -30,6 +34,8 @@ class PreferenceState extends Equatable {
|
|||||||
final bool useReader;
|
final bool useReader;
|
||||||
final bool markReadStories;
|
final bool markReadStories;
|
||||||
final bool showMetadata;
|
final bool showMetadata;
|
||||||
|
final FetchMode fetchMode;
|
||||||
|
final CommentsOrder order;
|
||||||
|
|
||||||
PreferenceState copyWith({
|
PreferenceState copyWith({
|
||||||
bool? showNotification,
|
bool? showNotification,
|
||||||
@ -40,6 +46,8 @@ class PreferenceState extends Equatable {
|
|||||||
bool? useReader,
|
bool? useReader,
|
||||||
bool? markReadStories,
|
bool? markReadStories,
|
||||||
bool? showMetadata,
|
bool? showMetadata,
|
||||||
|
FetchMode? fetchMode,
|
||||||
|
CommentsOrder? order,
|
||||||
}) {
|
}) {
|
||||||
return PreferenceState(
|
return PreferenceState(
|
||||||
showNotification: showNotification ?? this.showNotification,
|
showNotification: showNotification ?? this.showNotification,
|
||||||
@ -50,6 +58,8 @@ class PreferenceState extends Equatable {
|
|||||||
useReader: useReader ?? this.useReader,
|
useReader: useReader ?? this.useReader,
|
||||||
markReadStories: markReadStories ?? this.markReadStories,
|
markReadStories: markReadStories ?? this.markReadStories,
|
||||||
showMetadata: showMetadata ?? this.showMetadata,
|
showMetadata: showMetadata ?? this.showMetadata,
|
||||||
|
fetchMode: fetchMode ?? this.fetchMode,
|
||||||
|
order: order ?? this.order,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,5 +73,7 @@ class PreferenceState extends Equatable {
|
|||||||
useReader,
|
useReader,
|
||||||
markReadStories,
|
markReadStories,
|
||||||
showMetadata,
|
showMetadata,
|
||||||
|
fetchMode,
|
||||||
|
order,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
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>();
|
||||||
@ -9,4 +11,48 @@ extension TryReadContext on BuildContext {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rect? get rect {
|
||||||
|
final RenderBox? box = findRenderObject() as RenderBox?;
|
||||||
|
final Rect? rect =
|
||||||
|
box == null ? null : box.localToGlobal(Offset.zero) & box.size;
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double _screenWidth = 0;
|
||||||
|
static double _storyTileHeight = 0;
|
||||||
|
static int _storyTileMaxLines = 4;
|
||||||
|
static const double _screenWidthLowerBound = 430,
|
||||||
|
_screenWidthUpperBound = 850,
|
||||||
|
_picHeightLowerBound = 110,
|
||||||
|
_picHeightUpperBound = 128,
|
||||||
|
_smallPicHeight = 100,
|
||||||
|
_picHeightFactor = 0.3;
|
||||||
|
|
||||||
|
double get storyTileHeight {
|
||||||
|
final double screenWidth =
|
||||||
|
min(MediaQuery.of(this).size.height, MediaQuery.of(this).size.width);
|
||||||
|
|
||||||
|
if (screenWidth == _screenWidth) {
|
||||||
|
return _storyTileHeight;
|
||||||
|
} else {
|
||||||
|
_screenWidth = screenWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool showSmallerPreviewPic = screenWidth > _screenWidthLowerBound &&
|
||||||
|
screenWidth < _screenWidthUpperBound;
|
||||||
|
final double height = showSmallerPreviewPic
|
||||||
|
? _smallPicHeight
|
||||||
|
: (screenWidth * _picHeightFactor)
|
||||||
|
.clamp(_picHeightLowerBound, _picHeightUpperBound);
|
||||||
|
final int maxLines = height == _smallPicHeight ? 3 : 4;
|
||||||
|
_storyTileMaxLines = maxLines;
|
||||||
|
|
||||||
|
_storyTileHeight = height;
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get storyTileMaxLines {
|
||||||
|
return _storyTileMaxLines;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:hacki/models/item.dart';
|
import 'package:hacki/models/item.dart';
|
||||||
|
|
||||||
enum StoryType {
|
enum StoryType {
|
||||||
@ -93,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,
|
||||||
@ -113,9 +112,10 @@ class Story extends Item {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final String prettyString =
|
// final String prettyString =
|
||||||
const JsonEncoder.withIndent(' ').convert(this);
|
// const JsonEncoder.withIndent(' ').convert(this);
|
||||||
return 'Story $prettyString';
|
// return 'Story $prettyString';
|
||||||
|
return 'Story $id';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -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';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
|
import 'package:hacki/cubits/comments/comments_cubit.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:synced_shared_preferences/synced_shared_preferences.dart';
|
import 'package:synced_shared_preferences/synced_shared_preferences.dart';
|
||||||
@ -42,6 +43,8 @@ class PreferenceRepository {
|
|||||||
static const String _navigationModeKey = 'navigationMode';
|
static const String _navigationModeKey = 'navigationMode';
|
||||||
static const String _eyeCandyModeKey = 'eyeCandyMode';
|
static const String _eyeCandyModeKey = 'eyeCandyMode';
|
||||||
static const String _markReadStoriesModeKey = 'markReadStoriesMode';
|
static const String _markReadStoriesModeKey = 'markReadStoriesMode';
|
||||||
|
static const String _fetchModeKey = 'fetchMode';
|
||||||
|
static const String _commentsOrderKey = 'commentsOrder';
|
||||||
|
|
||||||
static const bool _notificationModeDefaultValue = true;
|
static const bool _notificationModeDefaultValue = true;
|
||||||
static const bool _displayModeDefaultValue = true;
|
static const bool _displayModeDefaultValue = true;
|
||||||
@ -53,6 +56,8 @@ class PreferenceRepository {
|
|||||||
static const bool _markReadStoriesModeDefaultValue = true;
|
static const bool _markReadStoriesModeDefaultValue = true;
|
||||||
static const bool _isFirstLaunchKeyDefaultValue = true;
|
static const bool _isFirstLaunchKeyDefaultValue = true;
|
||||||
static const bool _metadataModeDefaultValue = true;
|
static const bool _metadataModeDefaultValue = true;
|
||||||
|
static final int _fetchModeDefaultValue = FetchMode.eager.index;
|
||||||
|
static final int _commentsOrderDefaultValue = CommentsOrder.natural.index;
|
||||||
|
|
||||||
final SyncedSharedPreferences _syncedPrefs;
|
final SyncedSharedPreferences _syncedPrefs;
|
||||||
final Future<SharedPreferences> _prefs;
|
final Future<SharedPreferences> _prefs;
|
||||||
@ -120,6 +125,17 @@ class PreferenceRepository {
|
|||||||
_markReadStoriesModeDefaultValue,
|
_markReadStoriesModeDefaultValue,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<FetchMode> get fetchMode async => _prefs.then(
|
||||||
|
(SharedPreferences prefs) => FetchMode.values
|
||||||
|
.elementAt(prefs.getInt(_fetchModeKey) ?? _fetchModeDefaultValue),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<CommentsOrder> get commentsOrder async => _prefs.then(
|
||||||
|
(SharedPreferences prefs) => CommentsOrder.values.elementAt(
|
||||||
|
prefs.getInt(_commentsOrderKey) ?? _commentsOrderDefaultValue,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Future<bool> hasPushed(int commentId) async =>
|
Future<bool> hasPushed(int commentId) async =>
|
||||||
_prefs.then((SharedPreferences prefs) {
|
_prefs.then((SharedPreferences prefs) {
|
||||||
final bool? val = prefs.getBool(_getPushNotificationKey(commentId));
|
final bool? val = prefs.getBool(_getPushNotificationKey(commentId));
|
||||||
@ -237,6 +253,18 @@ class PreferenceRepository {
|
|||||||
await prefs.setBool(_metadataModeKey, !currentMode);
|
await prefs.setBool(_metadataModeKey, !currentMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> selectFetchMode(FetchMode fetchMode) async {
|
||||||
|
final SharedPreferences prefs = await _prefs;
|
||||||
|
final int index = fetchMode.index;
|
||||||
|
await prefs.setInt(_fetchModeKey, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selectCommentsOrder(CommentsOrder order) async {
|
||||||
|
final SharedPreferences prefs = await _prefs;
|
||||||
|
final int index = order.index;
|
||||||
|
await prefs.setInt(_commentsOrderKey, index);
|
||||||
|
}
|
||||||
|
|
||||||
//#region fav
|
//#region fav
|
||||||
|
|
||||||
Future<List<int>> favList({required String of}) async {
|
Future<List<int>> favList({required String of}) async {
|
||||||
|
@ -68,8 +68,33 @@ class StoriesRepository {
|
|||||||
|
|
||||||
if (comment != null) {
|
if (comment != null) {
|
||||||
yield comment;
|
yield comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
yield* fetchCommentsStream(
|
Stream<Comment> fetchAllCommentsRecursivelyStream({
|
||||||
|
required List<int> ids,
|
||||||
|
int level = 0,
|
||||||
|
Comment? Function(int)? getFromCache,
|
||||||
|
}) async* {
|
||||||
|
for (final int id in ids) {
|
||||||
|
Comment? comment = getFromCache?.call(id)?.copyWith(level: level);
|
||||||
|
|
||||||
|
comment ??= await _firebaseClient
|
||||||
|
.get('${_baseUrl}item/$id.json')
|
||||||
|
.then((dynamic json) => _parseJson(json as Map<String, dynamic>?))
|
||||||
|
.then((Map<String, dynamic>? json) async {
|
||||||
|
if (json == null) return null;
|
||||||
|
|
||||||
|
final Comment comment = Comment.fromJson(json, level: level);
|
||||||
|
return comment;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (comment != null) {
|
||||||
|
yield comment;
|
||||||
|
|
||||||
|
yield* fetchAllCommentsRecursivelyStream(
|
||||||
ids: comment.kids,
|
ids: comment.kids,
|
||||||
level: level + 1,
|
level: level + 1,
|
||||||
getFromCache: getFromCache,
|
getFromCache: getFromCache,
|
||||||
|
@ -452,7 +452,10 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
locator.get<StoriesRepository>().fetchItemBy(id: id).then((Item? item) {
|
locator.get<StoriesRepository>().fetchItemBy(id: id).then((Item? item) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
goToItemScreen(args: ItemScreenArgs(item: item));
|
goToItemScreen(
|
||||||
|
args: ItemScreenArgs(item: item),
|
||||||
|
forceNewScreen: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -86,16 +86,16 @@ class ItemScreen extends StatefulWidget {
|
|||||||
context.read<StoriesBloc>().state.offlineReading,
|
context.read<StoriesBloc>().state.offlineReading,
|
||||||
item: args.item,
|
item: args.item,
|
||||||
collapseCache: context.read<CollapseCache>(),
|
collapseCache: context.read<CollapseCache>(),
|
||||||
|
defaultFetchMode:
|
||||||
|
context.read<PreferenceCubit>().state.fetchMode,
|
||||||
|
defaultCommentsOrder:
|
||||||
|
context.read<PreferenceCubit>().state.order,
|
||||||
)..init(
|
)..init(
|
||||||
onlyShowTargetComment: args.onlyShowTargetComment,
|
onlyShowTargetComment: args.onlyShowTargetComment,
|
||||||
targetParents: args.targetComments,
|
targetParents: args.targetComments,
|
||||||
useCommentCache: args.useCommentCache,
|
useCommentCache: args.useCommentCache,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<EditCubit>(
|
|
||||||
lazy: false,
|
|
||||||
create: (BuildContext context) => EditCubit(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: ItemScreen(
|
child: ItemScreen(
|
||||||
item: args.item,
|
item: args.item,
|
||||||
@ -128,15 +128,15 @@ class ItemScreen extends StatefulWidget {
|
|||||||
context.read<StoriesBloc>().state.offlineReading,
|
context.read<StoriesBloc>().state.offlineReading,
|
||||||
item: args.item,
|
item: args.item,
|
||||||
collapseCache: context.read<CollapseCache>(),
|
collapseCache: context.read<CollapseCache>(),
|
||||||
|
defaultFetchMode:
|
||||||
|
context.read<PreferenceCubit>().state.fetchMode,
|
||||||
|
defaultCommentsOrder:
|
||||||
|
context.read<PreferenceCubit>().state.order,
|
||||||
)..init(
|
)..init(
|
||||||
onlyShowTargetComment: args.onlyShowTargetComment,
|
onlyShowTargetComment: args.onlyShowTargetComment,
|
||||||
targetParents: args.targetComments,
|
targetParents: args.targetComments,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<EditCubit>(
|
|
||||||
lazy: false,
|
|
||||||
create: (BuildContext context) => EditCubit(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: ItemScreen(
|
child: ItemScreen(
|
||||||
item: args.item,
|
item: args.item,
|
||||||
@ -156,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();
|
||||||
@ -177,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,
|
||||||
@ -192,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(() {
|
||||||
@ -200,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
|
||||||
@ -307,7 +327,13 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoading: context.read<CommentsCubit>().loadMore,
|
onLoading: () {
|
||||||
|
if (state.fetchMode == FetchMode.eager) {
|
||||||
|
context.read<CommentsCubit>().loadMore();
|
||||||
|
} else {
|
||||||
|
refreshController.loadComplete();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
primary: false,
|
primary: false,
|
||||||
itemCount: state.comments.length + 2,
|
itemCount: state.comments.length + 2,
|
||||||
@ -349,7 +375,8 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
icon: Icons.message,
|
icon: Icons.message,
|
||||||
),
|
),
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (_) => onMoreTapped(state.item),
|
onPressed: (BuildContext context) =>
|
||||||
|
onMoreTapped(state.item, context.rect),
|
||||||
backgroundColor: Palette.orange,
|
backgroundColor: Palette.orange,
|
||||||
foregroundColor: Palette.white,
|
foregroundColor: Palette.white,
|
||||||
icon: Icons.more_horiz,
|
icon: Icons.more_horiz,
|
||||||
@ -486,6 +513,9 @@ 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(
|
||||||
|
fontSize: TextDimens.pt13,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
] else ...<Widget>[
|
] else ...<Widget>[
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@ -505,10 +535,46 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
strokeWidth: Dimens.pt2,
|
strokeWidth: Dimens.pt2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const Text('View parent thread'),
|
: const Text(
|
||||||
|
'View parent thread',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: TextDimens.pt13,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
if (!state.offlineReading)
|
||||||
|
DropdownButton<FetchMode>(
|
||||||
|
value: state.fetchMode,
|
||||||
|
underline: const SizedBox.shrink(),
|
||||||
|
items: const <DropdownMenuItem<FetchMode>>[
|
||||||
|
DropdownMenuItem<FetchMode>(
|
||||||
|
value: FetchMode.lazy,
|
||||||
|
child: Text(
|
||||||
|
'Lazy',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: TextDimens.pt13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<FetchMode>(
|
||||||
|
value: FetchMode.eager,
|
||||||
|
child: Text(
|
||||||
|
'Eager',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: TextDimens.pt13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: context
|
||||||
|
.read<CommentsCubit>()
|
||||||
|
.onFetchModeChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: Dimens.pt6,
|
||||||
|
),
|
||||||
DropdownButton<CommentsOrder>(
|
DropdownButton<CommentsOrder>(
|
||||||
value: state.order,
|
value: state.order,
|
||||||
underline: const SizedBox.shrink(),
|
underline: const SizedBox.shrink(),
|
||||||
@ -519,7 +585,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Natural',
|
'Natural',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: TextDimens.pt14,
|
fontSize: TextDimens.pt13,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -528,7 +594,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Newest first',
|
'Newest first',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: TextDimens.pt14,
|
fontSize: TextDimens.pt13,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -537,7 +603,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Oldest first',
|
'Oldest first',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: TextDimens.pt14,
|
fontSize: TextDimens.pt13,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -595,6 +661,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
myUsername:
|
myUsername:
|
||||||
authState.isLoggedIn ? authState.username : null,
|
authState.isLoggedIn ? authState.username : null,
|
||||||
opUsername: state.item.by,
|
opUsername: state.item.by,
|
||||||
|
fetchMode: state.fetchMode,
|
||||||
onReplyTapped: (Comment cmt) {
|
onReplyTapped: (Comment cmt) {
|
||||||
HapticFeedback.lightImpact();
|
HapticFeedback.lightImpact();
|
||||||
if (cmt.deleted || cmt.dead) {
|
if (cmt.deleted || cmt.dead) {
|
||||||
@ -630,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 ||
|
||||||
@ -854,6 +922,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
context.read<AuthBloc>().state.username,
|
context.read<AuthBloc>().state.username,
|
||||||
onStoryLinkTapped: onStoryLinkTapped,
|
onStoryLinkTapped: onStoryLinkTapped,
|
||||||
actionable: false,
|
actionable: false,
|
||||||
|
fetchMode: FetchMode.eager,
|
||||||
),
|
),
|
||||||
const Divider(
|
const Divider(
|
||||||
height: Dimens.zero,
|
height: Dimens.zero,
|
||||||
@ -895,7 +964,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMoreTapped(Item item) {
|
void onMoreTapped(Item item, Rect? rect) {
|
||||||
HapticFeedback.lightImpact();
|
HapticFeedback.lightImpact();
|
||||||
|
|
||||||
if (item.dead || item.deleted) {
|
if (item.dead || item.deleted) {
|
||||||
@ -1104,7 +1173,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
case _MenuAction.downvote:
|
case _MenuAction.downvote:
|
||||||
break;
|
break;
|
||||||
case _MenuAction.share:
|
case _MenuAction.share:
|
||||||
onShareTapped(item);
|
onShareTapped(item, rect);
|
||||||
break;
|
break;
|
||||||
case _MenuAction.flag:
|
case _MenuAction.flag:
|
||||||
onFlagTapped(item);
|
onFlagTapped(item);
|
||||||
@ -1119,8 +1188,12 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onShareTapped(Item item) =>
|
void onShareTapped(Item item, Rect? rect) {
|
||||||
Share.share('https://news.ycombinator.com/item?id=${item.id}');
|
Share.share(
|
||||||
|
'https://news.ycombinator.com/item?id=${item.id}',
|
||||||
|
sharePositionOrigin: rect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void onFlagTapped(Item item) {
|
void onFlagTapped(Item item) {
|
||||||
showDialog<bool>(
|
showDialog<bool>(
|
||||||
|
@ -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()),
|
||||||
|
@ -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(() {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -283,6 +283,122 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
},
|
},
|
||||||
activeColor: Palette.orange,
|
activeColor: Palette.orange,
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: Dimens.pt8,
|
||||||
|
),
|
||||||
|
Flex(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
children: const <Widget>[
|
||||||
|
SizedBox(
|
||||||
|
width: Dimens.pt16,
|
||||||
|
),
|
||||||
|
Text('Default fetch mode'),
|
||||||
|
Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
children: const <Widget>[
|
||||||
|
Text('Default comments order'),
|
||||||
|
Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Flex(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const SizedBox(
|
||||||
|
width: Dimens.pt16,
|
||||||
|
),
|
||||||
|
DropdownButton<FetchMode>(
|
||||||
|
value: preferenceState.fetchMode,
|
||||||
|
underline: const SizedBox.shrink(),
|
||||||
|
items: const <
|
||||||
|
DropdownMenuItem<FetchMode>>[
|
||||||
|
DropdownMenuItem<FetchMode>(
|
||||||
|
value: FetchMode.lazy,
|
||||||
|
child: Text(
|
||||||
|
'Lazy',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: TextDimens.pt16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<FetchMode>(
|
||||||
|
value: FetchMode.eager,
|
||||||
|
child: Text(
|
||||||
|
'Eager',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: TextDimens.pt16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: context
|
||||||
|
.read<PreferenceCubit>()
|
||||||
|
.selectFetchMode,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
DropdownButton<CommentsOrder>(
|
||||||
|
value: preferenceState.order,
|
||||||
|
underline: const SizedBox.shrink(),
|
||||||
|
items: const <
|
||||||
|
DropdownMenuItem<CommentsOrder>>[
|
||||||
|
DropdownMenuItem<CommentsOrder>(
|
||||||
|
value: CommentsOrder.natural,
|
||||||
|
child: Text(
|
||||||
|
'Natural',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: TextDimens.pt16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<CommentsOrder>(
|
||||||
|
value: CommentsOrder.newestFirst,
|
||||||
|
child: Text(
|
||||||
|
'Newest first',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: TextDimens.pt16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<CommentsOrder>(
|
||||||
|
value: CommentsOrder.oldestFirst,
|
||||||
|
child: Text(
|
||||||
|
'Oldest first',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: TextDimens.pt16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: context
|
||||||
|
.read<PreferenceCubit>()
|
||||||
|
.selectCommentsOrder,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: const Text('Complex Story Tile'),
|
title: const Text('Complex Story Tile'),
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
@ -410,7 +526,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
showAboutDialog(
|
showAboutDialog(
|
||||||
context: context,
|
context: context,
|
||||||
applicationName: 'Hacki',
|
applicationName: 'Hacki',
|
||||||
applicationVersion: 'v0.2.26',
|
applicationVersion: 'v0.2.32',
|
||||||
applicationIcon: ClipRRect(
|
applicationIcon: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(
|
||||||
Radius.circular(
|
Radius.circular(
|
||||||
|
@ -17,6 +17,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
required this.myUsername,
|
required this.myUsername,
|
||||||
required this.comment,
|
required this.comment,
|
||||||
required this.onStoryLinkTapped,
|
required this.onStoryLinkTapped,
|
||||||
|
required this.fetchMode,
|
||||||
this.onReplyTapped,
|
this.onReplyTapped,
|
||||||
this.onMoreTapped,
|
this.onMoreTapped,
|
||||||
this.onEditTapped,
|
this.onEditTapped,
|
||||||
@ -32,10 +33,11 @@ class CommentTile extends StatelessWidget {
|
|||||||
final int level;
|
final int level;
|
||||||
final bool actionable;
|
final bool actionable;
|
||||||
final Function(Comment)? onReplyTapped;
|
final Function(Comment)? onReplyTapped;
|
||||||
final Function(Comment)? onMoreTapped;
|
final Function(Comment, Rect?)? onMoreTapped;
|
||||||
final Function(Comment)? onEditTapped;
|
final Function(Comment)? onEditTapped;
|
||||||
final Function(Comment)? onRightMoreTapped;
|
final Function(Comment)? onRightMoreTapped;
|
||||||
final Function(String) onStoryLinkTapped;
|
final Function(String) onStoryLinkTapped;
|
||||||
|
final FetchMode fetchMode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -88,8 +90,11 @@ class CommentTile extends StatelessWidget {
|
|||||||
icon: Icons.edit,
|
icon: Icons.edit,
|
||||||
),
|
),
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (_) =>
|
onPressed: (BuildContext context) =>
|
||||||
onMoreTapped?.call(comment),
|
onMoreTapped?.call(
|
||||||
|
comment,
|
||||||
|
context.rect,
|
||||||
|
),
|
||||||
backgroundColor: Palette.orange,
|
backgroundColor: Palette.orange,
|
||||||
foregroundColor: Palette.white,
|
foregroundColor: Palette.white,
|
||||||
icon: Icons.more_horiz,
|
icon: Icons.more_horiz,
|
||||||
@ -277,6 +282,47 @@ class CommentTile extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (!state.collapsed &&
|
||||||
|
fetchMode == FetchMode.lazy &&
|
||||||
|
comment.kids.isNotEmpty &&
|
||||||
|
!context
|
||||||
|
.read<CommentsCubit>()
|
||||||
|
.state
|
||||||
|
.commentIds
|
||||||
|
.contains(comment.kids.first) &&
|
||||||
|
!context
|
||||||
|
.read<CommentsCubit>()
|
||||||
|
.state
|
||||||
|
.onlyShowTargetComment)
|
||||||
|
Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: Dimens.pt12,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
context
|
||||||
|
.read<CommentsCubit>()
|
||||||
|
.loadMore(comment: comment);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'''Load ${comment.kids.length} ${comment.kids.length > 1 ? 'replies' : 'reply'}''',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: TextDimens.pt12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
const Divider(
|
const Divider(
|
||||||
height: Dimens.zero,
|
height: Dimens.zero,
|
||||||
),
|
),
|
||||||
@ -298,7 +344,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
: Palette.transparent;
|
: Palette.transparent;
|
||||||
final bool isMyComment = myUsername == comment.by;
|
final bool isMyComment = myUsername == comment.by;
|
||||||
|
|
||||||
Widget? wrapper = child;
|
Widget wrapper = child;
|
||||||
|
|
||||||
if (isMyComment && level == 0) {
|
if (isMyComment && level == 0) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -311,6 +357,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
final Color wrapperBorderColor = _getColor(i);
|
final Color wrapperBorderColor = _getColor(i);
|
||||||
final bool shouldHighlight = isMyComment && i == level;
|
final bool shouldHighlight = isMyComment && i == level;
|
||||||
wrapper = Container(
|
wrapper = Container(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
margin: const EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
left: Dimens.pt12,
|
left: Dimens.pt12,
|
||||||
),
|
),
|
||||||
@ -330,7 +377,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapper!;
|
return wrapper;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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() {
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/screens/widgets/link_preview/link_view.dart';
|
import 'package:hacki/screens/widgets/link_preview/link_view.dart';
|
||||||
import 'package:hacki/screens/widgets/link_preview/web_analyzer.dart';
|
import 'package:hacki/screens/widgets/link_preview/web_analyzer.dart';
|
||||||
@ -199,23 +200,9 @@ class _LinkPreviewState extends State<LinkPreview> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const double screenWidthLowerBound = 428,
|
|
||||||
screenWidthUpperBound = 850,
|
|
||||||
picHeightLowerBound = 118,
|
|
||||||
picHeightUpperBound = 140,
|
|
||||||
smallPicHeight = 100,
|
|
||||||
picHeightFactor = 0.14;
|
|
||||||
final double screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
final bool showSmallerPreviewPic = screenWidth > screenWidthLowerBound &&
|
|
||||||
screenWidth < screenWidthUpperBound;
|
|
||||||
final double height = showSmallerPreviewPic
|
|
||||||
? smallPicHeight
|
|
||||||
: (MediaQuery.of(context).size.height * picHeightFactor)
|
|
||||||
.clamp(picHeightLowerBound, picHeightUpperBound);
|
|
||||||
|
|
||||||
final Widget loadingWidget = widget.placeholderWidget ??
|
final Widget loadingWidget = widget.placeholderWidget ??
|
||||||
Container(
|
Container(
|
||||||
height: height,
|
height: context.storyTileHeight,
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
@ -232,13 +219,13 @@ class _LinkPreviewState extends State<LinkPreview> {
|
|||||||
final WebInfo? info = _info as WebInfo?;
|
final WebInfo? info = _info as WebInfo?;
|
||||||
loadedWidget = _info == null
|
loadedWidget = _info == null
|
||||||
? _buildLinkContainer(
|
? _buildLinkContainer(
|
||||||
height,
|
context.storyTileHeight,
|
||||||
title: _errorTitle,
|
title: _errorTitle,
|
||||||
desc: _errorBody,
|
desc: _errorBody,
|
||||||
imageUri: null,
|
imageUri: null,
|
||||||
)
|
)
|
||||||
: _buildLinkContainer(
|
: _buildLinkContainer(
|
||||||
height,
|
context.storyTileHeight,
|
||||||
title: _errorTitle,
|
title: _errorTitle,
|
||||||
desc: WebAnalyzer.isNotEmpty(info!.description)
|
desc: WebAnalyzer.isNotEmpty(info!.description)
|
||||||
? info.description
|
? info.description
|
||||||
|
@ -147,7 +147,7 @@ class LinkView extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildTitleContainer(TextStyle _titleTS, int _maxLines) {
|
Widget _buildTitleContainer(TextStyle _titleTS, int _maxLines) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(4, 2, 3, 1),
|
padding: const EdgeInsets.fromLTRB(4, 2, 3, 0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
@ -168,7 +168,7 @@ class LinkView extends StatelessWidget {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(5, 3, 5, 0),
|
padding: const EdgeInsets.fromLTRB(5, 2, 5, 0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (showMetadata)
|
if (showMetadata)
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_fadein/flutter_fadein.dart';
|
import 'package:flutter_fadein/flutter_fadein.dart';
|
||||||
import 'package:hacki/blocs/blocs.dart';
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
@ -29,20 +30,7 @@ class StoryTile extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (showWebPreview) {
|
if (showWebPreview) {
|
||||||
const double screenWidthLowerBound = 428,
|
final double height = context.storyTileHeight;
|
||||||
screenWidthUpperBound = 850,
|
|
||||||
picHeightLowerBound = 118,
|
|
||||||
picHeightUpperBound = 140,
|
|
||||||
smallPicHeight = 100,
|
|
||||||
picHeightFactor = 0.14;
|
|
||||||
final double screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
final bool showSmallerPreviewPic = screenWidth > screenWidthLowerBound &&
|
|
||||||
screenWidth < screenWidthUpperBound;
|
|
||||||
final double height = showSmallerPreviewPic
|
|
||||||
? smallPicHeight
|
|
||||||
: (MediaQuery.of(context).size.height * picHeightFactor)
|
|
||||||
.clamp(picHeightLowerBound, picHeightUpperBound);
|
|
||||||
|
|
||||||
return TapDownWrapper(
|
return TapDownWrapper(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -143,7 +131,7 @@ class StoryTile extends StatelessWidget {
|
|||||||
backgroundColor: Palette.transparent,
|
backgroundColor: Palette.transparent,
|
||||||
borderRadius: Dimens.zero,
|
borderRadius: Dimens.zero,
|
||||||
removeElevation: true,
|
removeElevation: true,
|
||||||
bodyMaxLines: height == smallPicHeight ? 3 : 4,
|
bodyMaxLines: context.storyTileMaxLines,
|
||||||
errorTitle: story.title,
|
errorTitle: story.title,
|
||||||
titleStyle: TextStyle(
|
titleStyle: TextStyle(
|
||||||
color: hasRead
|
color: hasRead
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
154
pubspec.lock
@ -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: "2.0.0"
|
||||||
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"
|
||||||
|
25
pubspec.yaml
@ -1,22 +1,22 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 0.2.26+68
|
version: 0.2.32+75
|
||||||
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.4"
|
||||||
|
|
||||||
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,16 +25,20 @@ 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: ^2.0.0
|
||||||
font_awesome_flutter: ^9.2.0
|
font_awesome_flutter: ^9.2.0
|
||||||
gbk_codec: ^0.4.0
|
gbk_codec: ^0.4.0
|
||||||
get_it: 7.2.0
|
get_it: 7.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:
|
||||||
|
29
test_driver/perf_driver.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|