Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
c685f33f99 | |||
518608893d | |||
856efa7c14 |
BIN
android/app/src/main/res/drawable-night-v21/background.png
Normal file
After Width: | Height: | Size: 69 B |
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
BIN
android/app/src/main/res/drawable-night/background.png
Normal file
After Width: | Height: | Size: 69 B |
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
BIN
android/app/src/main/res/drawable-v21/background.png
Normal file
After Width: | Height: | Size: 69 B |
@ -1,12 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="?android:colorBackground" />
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
<!-- You can insert your own image assets here -->
|
</item>
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
BIN
android/app/src/main/res/drawable/background.png
Normal file
After Width: | Height: | Size: 69 B |
@ -1,12 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@android:color/white" />
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
<!-- You can insert your own image assets here -->
|
</item>
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
19
android/app/src/main/res/values-night-v31/styles.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -5,6 +5,10 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
19
android/app/src/main/res/values-v31/styles.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -5,6 +5,10 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
7
assets/remote-config-dev.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"athingComtrSelector": "#hnmain > tbody > tr > td > table > tbody > .athing.comtr",
|
||||||
|
"commentTextSelector": "td > table > tbody > tr > td.default > div.comment > div.commtext",
|
||||||
|
"commentHeadSelector": "td > table > tbody > tr > td.default > div > span > a",
|
||||||
|
"commentAgeSelector": "td > table > tbody > tr > td.default > div > span > span.age",
|
||||||
|
"commentIndentSelector": "td > table > tbody > tr > td.ind"
|
||||||
|
}
|
@ -16,6 +16,8 @@ PODS:
|
|||||||
- OrderedSet (~> 5.0)
|
- OrderedSet (~> 5.0)
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_native_splash (0.0.1):
|
||||||
|
- Flutter
|
||||||
- flutter_secure_storage (6.0.0):
|
- flutter_secure_storage (6.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_siri_suggestions (0.0.1):
|
- flutter_siri_suggestions (0.0.1):
|
||||||
@ -62,6 +64,7 @@ DEPENDENCIES:
|
|||||||
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
|
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
|
||||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
- flutter_siri_suggestions (from `.symlinks/plugins/flutter_siri_suggestions/ios`)
|
- flutter_siri_suggestions (from `.symlinks/plugins/flutter_siri_suggestions/ios`)
|
||||||
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||||
@ -97,6 +100,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
|
flutter_native_splash:
|
||||||
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
flutter_siri_suggestions:
|
flutter_siri_suggestions:
|
||||||
@ -137,6 +142,7 @@ SPEC CHECKSUMS:
|
|||||||
flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40
|
flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40
|
||||||
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
||||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||||
|
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||||
flutter_siri_suggestions: 226fb7ef33d25d3fe0d4aa2a8bcf4b72730c466f
|
flutter_siri_suggestions: 226fb7ef33d25d3fe0d4aa2a8bcf4b72730c466f
|
||||||
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
||||||
|
22
ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "background.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "darkbackground.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png
vendored
Normal file
After Width: | Height: | Size: 69 B |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png
vendored
Normal file
After Width: | Height: | Size: 69 B |
@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage.png",
|
"filename" : "LaunchImage.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage@2x.png",
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage@3x.png",
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
@ -16,13 +16,19 @@
|
|||||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
|
||||||
</imageView>
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
|
||||||
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
|
||||||
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
@ -33,5 +39,6 @@
|
|||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LaunchImage" width="168" height="185"/>
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
<image name="LaunchBackground" width="1" height="1"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
@ -1,84 +1,86 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||||
<array>
|
<array>
|
||||||
<string>workmanager.background.task</string>
|
<string>workmanager.background.task</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Hacki</string>
|
<string>Hacki</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>hacki</string>
|
<string>hacki</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Editor</string>
|
<string>Editor</string>
|
||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>ShareMedia</string>
|
<string>ShareMedia</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>https</string>
|
<string>https</string>
|
||||||
<string>http</string>
|
<string>http</string>
|
||||||
<string>mailto</string>
|
<string>mailto</string>
|
||||||
</array>
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>com.apple.developer.associated-domains</key>
|
<key>com.apple.developer.associated-domains</key>
|
||||||
<array>
|
<array>
|
||||||
<string>applinks:example.com</string>
|
<string>applinks:example.com</string>
|
||||||
</array>
|
</array>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>This app needs camera access to scan QR codes</string>
|
<string>This app needs camera access to scan QR codes</string>
|
||||||
<key>io.flutter.embedded_views_preview</key>
|
<key>io.flutter.embedded_views_preview</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
<key>UIStatusBarHidden</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -69,6 +69,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
static const int _largePageSize = 20;
|
static const int _largePageSize = 20;
|
||||||
static const int _tabletSmallPageSize = 15;
|
static const int _tabletSmallPageSize = 15;
|
||||||
static const int _tabletLargePageSize = 25;
|
static const int _tabletLargePageSize = 25;
|
||||||
|
static const String _logPrefix = '[StoriesBloc]';
|
||||||
|
|
||||||
Future<void> onInitialize(
|
Future<void> onInitialize(
|
||||||
StoriesInitialize event,
|
StoriesInitialize event,
|
||||||
@ -245,7 +246,9 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
|
|
||||||
final Story story = event.story;
|
final Story story = event.story;
|
||||||
if (state.storiesByType[event.type]?.contains(story) ?? false) {
|
if (state.storiesByType[event.type]?.contains(story) ?? false) {
|
||||||
_logger.d('story already exists.');
|
_logger.d(
|
||||||
|
'$_logPrefix story ${story.id} for ${event.type} already exists.',
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final bool hasRead = await _preferenceRepository.hasRead(story.id);
|
final bool hasRead = await _preferenceRepository.hasRead(story.id);
|
||||||
@ -349,20 +352,20 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
<StreamSubscription<Comment>>[];
|
<StreamSubscription<Comment>>[];
|
||||||
for (final int id in ids) {
|
for (final int id in ids) {
|
||||||
if (state.downloadStatus == StoriesDownloadStatus.canceled) {
|
if (state.downloadStatus == StoriesDownloadStatus.canceled) {
|
||||||
_logger.d('aborting downloading');
|
_logger.d('$_logPrefix aborting downloading');
|
||||||
|
|
||||||
for (final StreamSubscription<Comment> stream in downloadStreams) {
|
for (final StreamSubscription<Comment> stream in downloadStreams) {
|
||||||
await stream.cancel();
|
await stream.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.d('deleting downloaded contents');
|
_logger.d('$_logPrefix deleting downloaded contents');
|
||||||
await _offlineRepository.deleteAllStoryIds();
|
await _offlineRepository.deleteAllStoryIds();
|
||||||
await _offlineRepository.deleteAllStories();
|
await _offlineRepository.deleteAllStories();
|
||||||
await _offlineRepository.deleteAllComments();
|
await _offlineRepository.deleteAllComments();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.d('fetching story $id');
|
_logger.d('$_logPrefix fetching story $id');
|
||||||
final Story? story = await _hackerNewsRepository.fetchStory(id: id);
|
final Story? story = await _hackerNewsRepository.fetchStory(id: id);
|
||||||
|
|
||||||
if (story == null) {
|
if (story == null) {
|
||||||
@ -382,7 +385,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
await _offlineRepository.cacheStory(story: story);
|
await _offlineRepository.cacheStory(story: story);
|
||||||
|
|
||||||
if (story.url.isNotEmpty && includingWebPage) {
|
if (story.url.isNotEmpty && includingWebPage) {
|
||||||
_logger.i('downloading ${story.url}');
|
_logger.i('$_logPrefix downloading ${story.url}');
|
||||||
await _offlineRepository.cacheUrl(url: story.url);
|
await _offlineRepository.cacheUrl(url: story.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,19 +402,19 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
.listen(
|
.listen(
|
||||||
(Comment comment) {
|
(Comment comment) {
|
||||||
if (state.downloadStatus == StoriesDownloadStatus.canceled) {
|
if (state.downloadStatus == StoriesDownloadStatus.canceled) {
|
||||||
_logger.d('aborting downloading from comments stream');
|
_logger.d('$_logPrefix aborting downloading from comments stream');
|
||||||
downloadStream?.cancel();
|
downloadStream?.cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.d('fetched comment ${comment.id}');
|
_logger.d('$_logPrefix fetched comment ${comment.id}');
|
||||||
unawaited(
|
unawaited(
|
||||||
_offlineRepository.cacheComment(comment: comment),
|
_offlineRepository.cacheComment(comment: comment),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)..onDone(() {
|
)..onDone(() {
|
||||||
_logger.d(
|
_logger.d(
|
||||||
'''finished downloading story ${story.id} with ${story.descendants} comments''',
|
'''$_logPrefix finished downloading story ${story.id} with ${story.descendants} comments''',
|
||||||
);
|
);
|
||||||
add(StoryDownloaded(skipped: false));
|
add(StoryDownloaded(skipped: false));
|
||||||
});
|
});
|
||||||
|
@ -84,6 +84,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
<int, StreamSubscription<Comment>>{};
|
<int, StreamSubscription<Comment>>{};
|
||||||
|
|
||||||
static const int _webFetchingCmtCountLowerLimit = 5;
|
static const int _webFetchingCmtCountLowerLimit = 5;
|
||||||
|
static const String _logPrefix = '[CommentsCubit]';
|
||||||
|
|
||||||
Future<bool> get _shouldFetchFromWeb async {
|
Future<bool> get _shouldFetchFromWeb async {
|
||||||
final bool isOnWifi = await _isOnWifi;
|
final bool isOnWifi = await _isOnWifi;
|
||||||
@ -182,13 +183,14 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
case CommentsOrder.natural:
|
case CommentsOrder.natural:
|
||||||
final bool shouldFetchFromWeb = await _shouldFetchFromWeb;
|
final bool shouldFetchFromWeb = await _shouldFetchFromWeb;
|
||||||
if (fetchFromWeb && shouldFetchFromWeb) {
|
if (fetchFromWeb && shouldFetchFromWeb) {
|
||||||
_logger.d('fetching from web.');
|
_logger
|
||||||
|
.d('$_logPrefix fetching comments of ${item.id} from web.');
|
||||||
commentStream = _hackerNewsWebRepository
|
commentStream = _hackerNewsWebRepository
|
||||||
.fetchCommentsStream(state.item)
|
.fetchCommentsStream(state.item)
|
||||||
.handleError((dynamic e) {
|
.handleError((dynamic e) {
|
||||||
_streamSubscription?.cancel();
|
_streamSubscription?.cancel();
|
||||||
|
|
||||||
_logger.e(e);
|
_logger.e('$_logPrefix $e');
|
||||||
|
|
||||||
switch (e.runtimeType) {
|
switch (e.runtimeType) {
|
||||||
case RateLimitedException:
|
case RateLimitedException:
|
||||||
@ -205,7 +207,8 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_logger.d('fetching from API.');
|
_logger
|
||||||
|
.d('$_logPrefix fetching comments of ${item.id} from API.');
|
||||||
commentStream =
|
commentStream =
|
||||||
_hackerNewsRepository.fetchAllCommentsRecursivelyStream(
|
_hackerNewsRepository.fetchAllCommentsRecursivelyStream(
|
||||||
ids: kids,
|
ids: kids,
|
||||||
@ -280,11 +283,13 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
case CommentsOrder.natural:
|
case CommentsOrder.natural:
|
||||||
final bool shouldFetchFromWeb = await _shouldFetchFromWeb;
|
final bool shouldFetchFromWeb = await _shouldFetchFromWeb;
|
||||||
if (fetchFromWeb && shouldFetchFromWeb) {
|
if (fetchFromWeb && shouldFetchFromWeb) {
|
||||||
_logger.d('fetching from web.');
|
_logger.d(
|
||||||
|
'$_logPrefix fetching comments of ${item.id} from web.',
|
||||||
|
);
|
||||||
commentStream = _hackerNewsWebRepository
|
commentStream = _hackerNewsWebRepository
|
||||||
.fetchCommentsStream(state.item)
|
.fetchCommentsStream(state.item)
|
||||||
.handleError((dynamic e) {
|
.handleError((dynamic e) {
|
||||||
_logger.e(e);
|
_logger.e('$_logPrefix $e');
|
||||||
|
|
||||||
switch (e.runtimeType) {
|
switch (e.runtimeType) {
|
||||||
case RateLimitedException:
|
case RateLimitedException:
|
||||||
@ -301,7 +306,8 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_logger.d('fetching from API.');
|
_logger
|
||||||
|
.d('$_logPrefix fetching comments of ${item.id} from API.');
|
||||||
commentStream = _hackerNewsRepository
|
commentStream = _hackerNewsRepository
|
||||||
.fetchAllCommentsRecursivelyStream(ids: kids);
|
.fetchAllCommentsRecursivelyStream(ids: kids);
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,7 @@ class NotificationCubit extends Cubit<NotificationState> {
|
|||||||
static const Duration _refreshInterval = Duration(minutes: 5);
|
static const Duration _refreshInterval = Duration(minutes: 5);
|
||||||
static const int _subscriptionUpperLimit = 15;
|
static const int _subscriptionUpperLimit = 15;
|
||||||
static const int _pageSize = 20;
|
static const int _pageSize = 20;
|
||||||
|
static const String _logPrefix = '[NotificationCubit]';
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
emit(NotificationState.init());
|
emit(NotificationState.init());
|
||||||
@ -78,7 +79,7 @@ class NotificationCubit extends Cubit<NotificationState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await _preferenceRepository.unreadCommentsIds.then((List<int> unreadIds) {
|
await _preferenceRepository.unreadCommentsIds.then((List<int> unreadIds) {
|
||||||
_logger.i('NotificationCubit: ${unreadIds.length} unread items.');
|
_logger.i('$_logPrefix ${unreadIds.length} unread items.');
|
||||||
emit(state.copyWith(unreadCommentsIds: unreadIds));
|
emit(state.copyWith(unreadCommentsIds: unreadIds));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -104,31 +105,17 @@ class NotificationCubit extends Cubit<NotificationState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void markAsRead(int id) {
|
void markAsRead(int id) {
|
||||||
Future.doWhile(() {
|
if (state.unreadCommentsIds.contains(id)) {
|
||||||
if (state.status != Status.inProgress) {
|
final List<int> updatedUnreadIds = <int>[...state.unreadCommentsIds]
|
||||||
if (state.unreadCommentsIds.contains(id)) {
|
..remove(id);
|
||||||
final List<int> updatedUnreadIds = <int>[...state.unreadCommentsIds]
|
_preferenceRepository.updateUnreadCommentsIds(updatedUnreadIds);
|
||||||
..remove(id);
|
emit(state.copyWith(unreadCommentsIds: updatedUnreadIds));
|
||||||
_preferenceRepository.updateUnreadCommentsIds(updatedUnreadIds);
|
}
|
||||||
emit(state.copyWith(unreadCommentsIds: updatedUnreadIds));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void markAllAsRead() {
|
void markAllAsRead() {
|
||||||
Future.doWhile(() {
|
emit(state.copyWith(unreadCommentsIds: <int>[]));
|
||||||
if (state.status != Status.inProgress) {
|
_preferenceRepository.updateUnreadCommentsIds(<int>[]);
|
||||||
emit(state.copyWith(unreadCommentsIds: <int>[]));
|
|
||||||
_preferenceRepository.updateUnreadCommentsIds(<int>[]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
|
@ -23,6 +23,7 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
|||||||
|
|
||||||
final PreferenceRepository _preferenceRepository;
|
final PreferenceRepository _preferenceRepository;
|
||||||
final Logger _logger;
|
final Logger _logger;
|
||||||
|
static const String _logPrefix = '[PreferenceCubit]';
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
for (final BooleanPreference p
|
for (final BooleanPreference p
|
||||||
@ -73,7 +74,7 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void update<T>(Preference<T> preference) {
|
void update<T>(Preference<T> preference) {
|
||||||
_logger.i('updating $preference to ${preference.val}');
|
_logger.i('$_logPrefix updating $preference to ${preference.val}');
|
||||||
|
|
||||||
emit(state.copyWithPreference(preference));
|
emit(state.copyWithPreference(preference));
|
||||||
|
|
||||||
|
@ -3,25 +3,34 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
import 'package:hacki/repositories/remote_config_repository.dart';
|
import 'package:hacki/repositories/remote_config_repository.dart';
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
part 'remote_config_state.dart';
|
part 'remote_config_state.dart';
|
||||||
|
|
||||||
class RemoteConfigCubit extends HydratedCubit<RemoteConfigState> {
|
class RemoteConfigCubit extends HydratedCubit<RemoteConfigState> {
|
||||||
RemoteConfigCubit({RemoteConfigRepository? remoteConfigRepository})
|
RemoteConfigCubit({
|
||||||
: _remoteConfigRepository =
|
RemoteConfigRepository? remoteConfigRepository,
|
||||||
|
Logger? logger,
|
||||||
|
}) : _remoteConfigRepository =
|
||||||
remoteConfigRepository ?? locator.get<RemoteConfigRepository>(),
|
remoteConfigRepository ?? locator.get<RemoteConfigRepository>(),
|
||||||
|
_logger = logger ?? locator.get<Logger>(),
|
||||||
super(RemoteConfigState.init()) {
|
super(RemoteConfigState.init()) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
final RemoteConfigRepository _remoteConfigRepository;
|
final RemoteConfigRepository _remoteConfigRepository;
|
||||||
|
final Logger _logger;
|
||||||
|
static const String _logPrefix = '';
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
_remoteConfigRepository
|
_remoteConfigRepository
|
||||||
.fetchRemoteConfig()
|
.fetchRemoteConfig()
|
||||||
.then((Map<String, dynamic> data) {
|
.then((Map<String, dynamic> data) {
|
||||||
if (data.isNotEmpty) {
|
if (data.isNotEmpty) {
|
||||||
|
_logger.i('$_logPrefix remote config fetched: $data');
|
||||||
emit(state.copyWith(data: data));
|
emit(state.copyWith(data: data));
|
||||||
|
} else {
|
||||||
|
_logger.i('$_logPrefix empty remote config.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,10 @@ class SplitViewCubit extends Cubit<SplitViewState> {
|
|||||||
|
|
||||||
final Logger _logger;
|
final Logger _logger;
|
||||||
final CommentCache _commentCache;
|
final CommentCache _commentCache;
|
||||||
|
static const String _logPrefix = '[SplitViewCubit]';
|
||||||
|
|
||||||
void updateItemScreenArgs(ItemScreenArgs args) {
|
void updateItemScreenArgs(ItemScreenArgs args) {
|
||||||
_logger.i('resetting comments in CommentCache');
|
_logger.i('$_logPrefix resetting comments in CommentCache');
|
||||||
_commentCache.resetComments();
|
_commentCache.resetComments();
|
||||||
emit(state.copyWith(itemScreenArgs: args));
|
emit(state.copyWith(itemScreenArgs: args));
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,20 @@ class TabCubit extends Cubit<TabState> {
|
|||||||
|
|
||||||
final PreferenceCubit _preferenceCubit;
|
final PreferenceCubit _preferenceCubit;
|
||||||
final Logger _logger;
|
final Logger _logger;
|
||||||
|
static const String _logPrefix = '[TabCubit]';
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
final List<StoryType> tabs = _preferenceCubit.state.tabs;
|
final List<StoryType> tabs = _preferenceCubit.state.tabs;
|
||||||
|
|
||||||
_logger.i('updating tabs to $tabs');
|
_logger.i('$_logPrefix updating tabs to $tabs');
|
||||||
|
|
||||||
emit(state.copyWith(tabs: tabs));
|
emit(state.copyWith(tabs: tabs));
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(int startIndex, int endIndex) {
|
void update(int startIndex, int endIndex) {
|
||||||
_logger.d('updating ${state.tabs} by moving $startIndex to $endIndex');
|
_logger.d(
|
||||||
|
'$_logPrefix updating ${state.tabs} by moving $startIndex to $endIndex',
|
||||||
|
);
|
||||||
final StoryType tab = state.tabs.elementAt(startIndex);
|
final StoryType tab = state.tabs.elementAt(startIndex);
|
||||||
final List<StoryType> updatedTabs = List<StoryType>.from(state.tabs)
|
final List<StoryType> updatedTabs = List<StoryType>.from(state.tabs)
|
||||||
..insert(endIndex, tab)
|
..insert(endIndex, tab)
|
||||||
|
@ -164,7 +164,7 @@ final class AutoScrollModePreference extends BooleanPreference {
|
|||||||
const AutoScrollModePreference({bool? val})
|
const AutoScrollModePreference({bool? val})
|
||||||
: super(val: val ?? _autoScrollModeDefaultValue);
|
: super(val: val ?? _autoScrollModeDefaultValue);
|
||||||
|
|
||||||
static const bool _autoScrollModeDefaultValue = false;
|
static const bool _autoScrollModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AutoScrollModePreference copyWith({required bool? val}) {
|
AutoScrollModePreference copyWith({required bool? val}) {
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class RemoteConfigRepository {
|
class RemoteConfigRepository {
|
||||||
RemoteConfigRepository({Dio? dio}) : _dio = dio ?? Dio();
|
RemoteConfigRepository({Dio? dio}) : _dio = dio ?? Dio();
|
||||||
|
|
||||||
final Dio _dio;
|
final Dio _dio;
|
||||||
|
static const String _path =
|
||||||
|
'https://raw.githubusercontent.com/Livinglist/Hacki/master/assets/';
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchRemoteConfig() async {
|
Future<Map<String, dynamic>> fetchRemoteConfig() async {
|
||||||
|
const String fileName =
|
||||||
|
kReleaseMode ? 'remote-config.json' : 'remote-config-dev.json';
|
||||||
final Response<dynamic> response = await _dio.get(
|
final Response<dynamic> response = await _dio.get(
|
||||||
'https://raw.githubusercontent.com/Livinglist/Hacki/master/assets/remote-config.json',
|
'$_path$fileName',
|
||||||
);
|
);
|
||||||
final String data = response.data as String? ?? '';
|
final String data = response.data as String? ?? '';
|
||||||
final Map<String, dynamic> json = jsonDecode(data) as Map<String, dynamic>;
|
final Map<String, dynamic> json = jsonDecode(data) as Map<String, dynamic>;
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:hacki/config/locator.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:logger/logger.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:sembast/sembast.dart';
|
import 'package:sembast/sembast.dart';
|
||||||
@ -17,7 +19,8 @@ class SembastRepository {
|
|||||||
SembastRepository({
|
SembastRepository({
|
||||||
Database? database,
|
Database? database,
|
||||||
Database? cache,
|
Database? cache,
|
||||||
}) {
|
Logger? logger,
|
||||||
|
}) : _logger = logger ?? locator.get<Logger>() {
|
||||||
if (database == null) {
|
if (database == null) {
|
||||||
initializeDatabase();
|
initializeDatabase();
|
||||||
} else {
|
} else {
|
||||||
@ -31,6 +34,9 @@ class SembastRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Logger _logger;
|
||||||
|
static const String _logPrefix = '[SembastRepository]';
|
||||||
|
|
||||||
Database? _database;
|
Database? _database;
|
||||||
Database? _cache;
|
Database? _cache;
|
||||||
List<int>? _idsOfCommentsRepliedToMe;
|
List<int>? _idsOfCommentsRepliedToMe;
|
||||||
@ -44,6 +50,9 @@ class SembastRepository {
|
|||||||
final Directory dir = await getApplicationCacheDirectory();
|
final Directory dir = await getApplicationCacheDirectory();
|
||||||
await dir.create(recursive: true);
|
await dir.create(recursive: true);
|
||||||
final String dbPath = join(dir.path, 'hacki.db');
|
final String dbPath = join(dir.path, 'hacki.db');
|
||||||
|
final File file = File(dbPath);
|
||||||
|
final FileStat stat = file.statSync();
|
||||||
|
_logger.i('$_logPrefix hacki.db file size: ${stat.size / 1000000}MB');
|
||||||
final DatabaseFactory dbFactory = databaseFactoryIo;
|
final DatabaseFactory dbFactory = databaseFactoryIo;
|
||||||
final Database db = await dbFactory.openDatabase(dbPath);
|
final Database db = await dbFactory.openDatabase(dbPath);
|
||||||
_database = db;
|
_database = db;
|
||||||
@ -54,6 +63,9 @@ class SembastRepository {
|
|||||||
final Directory tempDir = await getTemporaryDirectory();
|
final Directory tempDir = await getTemporaryDirectory();
|
||||||
await tempDir.create(recursive: true);
|
await tempDir.create(recursive: true);
|
||||||
final String dbPath = join(tempDir.path, 'hacki_cache.db');
|
final String dbPath = join(tempDir.path, 'hacki_cache.db');
|
||||||
|
final File file = File(dbPath);
|
||||||
|
final FileStat stat = file.statSync();
|
||||||
|
_logger.i('$_logPrefix hacki_cache.db file size: ${stat.size / 1000000}MB');
|
||||||
final DatabaseFactory dbFactory = databaseFactoryIo;
|
final DatabaseFactory dbFactory = databaseFactoryIo;
|
||||||
final Database db = await dbFactory.openDatabase(dbPath);
|
final Database db = await dbFactory.openDatabase(dbPath);
|
||||||
_cache = db;
|
_cache = db;
|
||||||
|
@ -43,13 +43,14 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
late final StreamSubscription<String?> siriSuggestionStreamSubscription;
|
late final StreamSubscription<String?> siriSuggestionStreamSubscription;
|
||||||
|
|
||||||
static final int tabLength = StoryType.values.length + 1;
|
static final int tabLength = StoryType.values.length + 1;
|
||||||
|
static const String logPrefix = '[HomeScreen]';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didPopNext() {
|
void didPopNext() {
|
||||||
super.didPopNext();
|
super.didPopNext();
|
||||||
if (context.read<StoriesBloc>().deviceScreenType ==
|
if (context.read<StoriesBloc>().deviceScreenType ==
|
||||||
DeviceScreenType.mobile) {
|
DeviceScreenType.mobile) {
|
||||||
locator.get<Logger>().i('resetting comments in CommentCache');
|
locator.get<Logger>().i('$logPrefix resetting comments in CommentCache');
|
||||||
Future<void>.delayed(
|
Future<void>.delayed(
|
||||||
AppDurations.ms500,
|
AppDurations.ms500,
|
||||||
locator.get<CommentCache>().resetComments,
|
locator.get<CommentCache>().resetComments,
|
||||||
|
@ -24,6 +24,14 @@ class MobileHomeScreen extends StatelessWidget {
|
|||||||
bottom: Dimens.pt36,
|
bottom: Dimens.pt36,
|
||||||
height: Dimens.pt40,
|
height: Dimens.pt40,
|
||||||
child: CountdownReminder(),
|
child: CountdownReminder(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const Positioned(
|
||||||
|
left: Dimens.pt24,
|
||||||
|
right: Dimens.pt24,
|
||||||
|
bottom: Dimens.pt36,
|
||||||
|
height: Dimens.pt40,
|
||||||
|
child: DownloadProgressReminder(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -41,13 +41,22 @@ class TabletHomeScreen extends StatelessWidget {
|
|||||||
curve: Curves.elasticOut,
|
curve: Curves.elasticOut,
|
||||||
child: homeScreen,
|
child: homeScreen,
|
||||||
),
|
),
|
||||||
Positioned(
|
if (!context.read<ReminderCubit>().state.hasShown)
|
||||||
left: Dimens.pt24,
|
Positioned(
|
||||||
bottom: Dimens.pt36,
|
left: Dimens.pt24,
|
||||||
height: Dimens.pt40,
|
bottom: Dimens.pt36,
|
||||||
width: homeScreenWidth - Dimens.pt24,
|
height: Dimens.pt40,
|
||||||
child: const CountdownReminder(),
|
width: homeScreenWidth - Dimens.pt24,
|
||||||
),
|
child: const CountdownReminder(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Positioned(
|
||||||
|
left: Dimens.pt24,
|
||||||
|
bottom: Dimens.pt36,
|
||||||
|
height: Dimens.pt40,
|
||||||
|
width: homeScreenWidth - Dimens.pt24,
|
||||||
|
child: const DownloadProgressReminder(),
|
||||||
|
),
|
||||||
AnimatedPositioned(
|
AnimatedPositioned(
|
||||||
right: Dimens.zero,
|
right: Dimens.zero,
|
||||||
top: Dimens.zero,
|
top: Dimens.zero,
|
||||||
|
@ -32,7 +32,9 @@ class InboxView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (unreadCommentsIds.isNotEmpty)
|
if (context.read<NotificationCubit>().state.status !=
|
||||||
|
Status.inProgress &&
|
||||||
|
unreadCommentsIds.isNotEmpty)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: onMarkAllAsReadTapped,
|
onPressed: onMarkAllAsReadTapped,
|
||||||
child: const Text('Mark all as read'),
|
child: const Text('Mark all as read'),
|
||||||
|
@ -341,6 +341,13 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
|||||||
),
|
),
|
||||||
onTap: showClearCacheDialog,
|
onTap: showClearCacheDialog,
|
||||||
),
|
),
|
||||||
|
if (preferenceState.isDevModeEnabled)
|
||||||
|
ListTile(
|
||||||
|
title: const Text(
|
||||||
|
'Logs',
|
||||||
|
),
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('About'),
|
title: const Text('About'),
|
||||||
subtitle: const Text('nothing interesting here.'),
|
subtitle: const Text('nothing interesting here.'),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
@ -10,6 +11,7 @@ import 'package:hacki/screens/widgets/widgets.dart';
|
|||||||
import 'package:hacki/services/services.dart';
|
import 'package:hacki/services/services.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
import 'package:hacki/utils/utils.dart';
|
import 'package:hacki/utils/utils.dart';
|
||||||
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
|
|
||||||
class CommentTile extends StatelessWidget {
|
class CommentTile extends StatelessWidget {
|
||||||
const CommentTile({
|
const CommentTile({
|
||||||
@ -417,18 +419,28 @@ class CommentTile extends StatelessWidget {
|
|||||||
preferenceCubit.state.isAutoScrollEnabled) {
|
preferenceCubit.state.isAutoScrollEnabled) {
|
||||||
final CommentsCubit commentsCubit = context.read<CommentsCubit>();
|
final CommentsCubit commentsCubit = context.read<CommentsCubit>();
|
||||||
final List<Comment> comments = commentsCubit.state.comments;
|
final List<Comment> comments = commentsCubit.state.comments;
|
||||||
final int indexOfNextComment = comments.indexOf(comment) + 1;
|
final int indexOfComment = comments.indexOf(comment);
|
||||||
if (indexOfNextComment < comments.length) {
|
if (indexOfComment < comments.length) {
|
||||||
Future<void>.delayed(
|
final double? leadingEdge =
|
||||||
AppDurations.ms300,
|
commentsCubit.itemPositionsListener.itemPositions.value
|
||||||
() {
|
.singleWhereOrNull(
|
||||||
commentsCubit.itemScrollController.scrollTo(
|
(ItemPosition e) => e.index - 1 == indexOfComment,
|
||||||
index: indexOfNextComment,
|
)
|
||||||
alignment: 0.1,
|
?.itemLeadingEdge;
|
||||||
duration: AppDurations.ms300,
|
final bool willBeOutsideOfScreen =
|
||||||
);
|
leadingEdge != null && leadingEdge < 0.1;
|
||||||
},
|
if (willBeOutsideOfScreen) {
|
||||||
);
|
Future<void>.delayed(
|
||||||
|
AppDurations.ms200,
|
||||||
|
() {
|
||||||
|
commentsCubit.itemScrollController.scrollTo(
|
||||||
|
index: indexOfComment + 1,
|
||||||
|
alignment: 0.15,
|
||||||
|
duration: AppDurations.ms300,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
83
lib/screens/widgets/download_progress_reminder.dart
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_fadein/flutter_fadein.dart';
|
||||||
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
|
||||||
|
class DownloadProgressReminder extends StatefulWidget {
|
||||||
|
const DownloadProgressReminder({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DownloadProgressReminder> createState() =>
|
||||||
|
_DownloadProgressReminderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DownloadProgressReminderState extends State<DownloadProgressReminder>
|
||||||
|
with SingleTickerProviderStateMixin, ItemActionMixin {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocSelector<StoriesBloc, StoriesState,
|
||||||
|
(int, int, StoriesDownloadStatus)>(
|
||||||
|
selector: (StoriesState state) {
|
||||||
|
return (
|
||||||
|
state.storiesDownloaded,
|
||||||
|
state.storiesToBeDownloaded,
|
||||||
|
state.downloadStatus
|
||||||
|
);
|
||||||
|
},
|
||||||
|
builder: (BuildContext context, (int, int, StoriesDownloadStatus) state) {
|
||||||
|
final int storiesDownloaded = state.$1;
|
||||||
|
final int storiesToBeDownloaded = state.$2;
|
||||||
|
final StoriesDownloadStatus status = state.$3;
|
||||||
|
final double progress = storiesToBeDownloaded == 0
|
||||||
|
? 0
|
||||||
|
: storiesDownloaded / storiesToBeDownloaded;
|
||||||
|
final bool isVisible = status == StoriesDownloadStatus.downloading;
|
||||||
|
return Visibility(
|
||||||
|
visible: isVisible,
|
||||||
|
child: FadeIn(
|
||||||
|
child: Material(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(
|
||||||
|
Dimens.pt4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: Dimens.pt12,
|
||||||
|
top: Dimens.pt10,
|
||||||
|
right: Dimens.pt10,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
'Downloading all stories ($storiesDownloaded/$storiesToBeDownloaded)',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
fontSize: TextDimens.pt12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
LinearProgressIndicator(
|
||||||
|
value: progress,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ export 'custom_dropdown_menu.dart';
|
|||||||
export 'custom_linkify/custom_linkify.dart';
|
export 'custom_linkify/custom_linkify.dart';
|
||||||
export 'custom_tab_bar.dart';
|
export 'custom_tab_bar.dart';
|
||||||
export 'device_gesture_wrapper.dart';
|
export 'device_gesture_wrapper.dart';
|
||||||
|
export 'download_progress_reminder.dart';
|
||||||
export 'item_text.dart';
|
export 'item_text.dart';
|
||||||
export 'items_list_view.dart';
|
export 'items_list_view.dart';
|
||||||
export 'link_preview/link_preview.dart';
|
export 'link_preview/link_preview.dart';
|
||||||
|
@ -38,15 +38,15 @@ abstract class Fetcher {
|
|||||||
final Logger logger = Logger();
|
final Logger logger = Logger();
|
||||||
final PreferenceRepository preferenceRepository =
|
final PreferenceRepository preferenceRepository =
|
||||||
PreferenceRepository(logger: logger);
|
PreferenceRepository(logger: logger);
|
||||||
|
|
||||||
final AuthRepository authRepository = AuthRepository(
|
final AuthRepository authRepository = AuthRepository(
|
||||||
preferenceRepository: preferenceRepository,
|
preferenceRepository: preferenceRepository,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
);
|
);
|
||||||
|
|
||||||
final HackerNewsRepository hackerNewsRepository = HackerNewsRepository();
|
|
||||||
final SembastRepository sembastRepository = SembastRepository();
|
final SembastRepository sembastRepository = SembastRepository();
|
||||||
|
final HackerNewsRepository hackerNewsRepository = HackerNewsRepository(
|
||||||
|
sembastRepository: sembastRepository,
|
||||||
|
logger: logger,
|
||||||
|
);
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
|
@ -85,6 +85,7 @@ class WebAnalyzer {
|
|||||||
RegExp('(title|icon|description|image)', caseSensitive: false);
|
RegExp('(title|icon|description|image)', caseSensitive: false);
|
||||||
static final RegExp _lineReg = RegExp(r'[\n\r]| |>');
|
static final RegExp _lineReg = RegExp(r'[\n\r]| |>');
|
||||||
static final RegExp _spaceReg = RegExp(r'\s+');
|
static final RegExp _spaceReg = RegExp(r'\s+');
|
||||||
|
static const String _logPrefix = '[WebAnalyzer]';
|
||||||
|
|
||||||
static bool isEmpty(String? str) {
|
static bool isEmpty(String? str) {
|
||||||
return !isNotEmpty(str);
|
return !isNotEmpty(str);
|
||||||
@ -120,7 +121,7 @@ class WebAnalyzer {
|
|||||||
|
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
locator.get<Logger>().d('''
|
locator.get<Logger>().d('''
|
||||||
fetched mem cached metadata using key $key for $story:
|
$_logPrefix fetched mem cached metadata using key $key for $story:
|
||||||
${info.toJson()}
|
${info.toJson()}
|
||||||
''');
|
''');
|
||||||
return info;
|
return info;
|
||||||
@ -168,7 +169,7 @@ ${info.toJson()}
|
|||||||
/// [5] If there is file cache, move it to mem cache for later retrieval.
|
/// [5] If there is file cache, move it to mem cache for later retrieval.
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
locator.get<Logger>().d('''
|
locator.get<Logger>().d('''
|
||||||
fetched file cached metadata using key $key for $story:
|
$_logPrefix fetched file cached metadata using key $key for $story:
|
||||||
${info.toJson()}
|
${info.toJson()}
|
||||||
''');
|
''');
|
||||||
cacheMap[key] = info;
|
cacheMap[key] = info;
|
||||||
@ -189,7 +190,7 @@ ${info.toJson()}
|
|||||||
if (info is WebInfo) {
|
if (info is WebInfo) {
|
||||||
locator
|
locator
|
||||||
.get<Logger>()
|
.get<Logger>()
|
||||||
.d('caching metadata using key $key for $story.');
|
.d('$_logPrefix caching metadata using key $key for $story.');
|
||||||
unawaited(
|
unawaited(
|
||||||
locator.get<SembastRepository>().cacheMetadata(
|
locator.get<SembastRepository>().cacheMetadata(
|
||||||
key: key,
|
key: key,
|
||||||
|
@ -8,10 +8,12 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
|
|
||||||
abstract class LogUtil {
|
abstract class LogUtil {
|
||||||
static LogPrinter get logPrinter => kReleaseMode
|
static LogPrinter get logPrinter => kReleaseMode
|
||||||
? SimplePrinter(colors: false)
|
? SimplePrinter(
|
||||||
: PrettyPrinter(
|
|
||||||
methodCount: 0,
|
|
||||||
colors: false,
|
colors: false,
|
||||||
|
printTime: true,
|
||||||
|
)
|
||||||
|
: PrettyPrinter(
|
||||||
|
printTime: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
static LogOutput logOutput(File outputFile) => MultiOutput(
|
static LogOutput logOutput(File outputFile) => MultiOutput(
|
||||||
|
42
pubspec.lock
@ -33,6 +33,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.11"
|
version: "2.0.11"
|
||||||
|
ansicolor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.6.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -436,6 +452,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
flutter_native_splash:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_native_splash
|
||||||
|
sha256: aa06fec78de2190f3db4319dd60fdc8d12b2626e93ef9828633928c2dcaea840
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -612,6 +636,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.5"
|
version: "9.1.5"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.0"
|
||||||
in_app_review:
|
in_app_review:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1269,6 +1301,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
universal_io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_io
|
||||||
|
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
universal_platform:
|
universal_platform:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1543,4 +1583,4 @@ packages:
|
|||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.22.2"
|
flutter: ">=3.22.3"
|
||||||
|
24
pubspec.yaml
@ -1,11 +1,11 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 2.8.1+146
|
version: 2.8.2+147
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
flutter: "3.22.2"
|
flutter: "3.22.3"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
adaptive_theme: ^3.2.0
|
adaptive_theme: ^3.2.0
|
||||||
@ -35,6 +35,7 @@ dependencies:
|
|||||||
flutter_inappwebview: ^6.0.0
|
flutter_inappwebview: ^6.0.0
|
||||||
flutter_local_notifications: ^17.1.2
|
flutter_local_notifications: ^17.1.2
|
||||||
flutter_material_color_picker: ^1.2.0
|
flutter_material_color_picker: ^1.2.0
|
||||||
|
flutter_native_splash: ^2.4.1
|
||||||
flutter_secure_storage: ^9.2.2
|
flutter_secure_storage: ^9.2.2
|
||||||
flutter_siri_suggestions:
|
flutter_siri_suggestions:
|
||||||
git:
|
git:
|
||||||
@ -139,4 +140,23 @@ flutter:
|
|||||||
- asset: assets/fonts/atkinson_hyperlegible/AtkinsonHyperlegible-Bold.ttf
|
- asset: assets/fonts/atkinson_hyperlegible/AtkinsonHyperlegible-Bold.ttf
|
||||||
weight: 700
|
weight: 700
|
||||||
|
|
||||||
|
flutter_native_splash:
|
||||||
|
# This package generates native code to customize Flutter's default white native splash screen
|
||||||
|
# with background color and splash image.
|
||||||
|
# Customize the parameters below, and run the following command in the terminal:
|
||||||
|
# dart run flutter_native_splash:create
|
||||||
|
# To restore Flutter's default white splash screen, run the following command in the terminal:
|
||||||
|
# dart run flutter_native_splash:remove
|
||||||
|
|
||||||
|
# IMPORTANT NOTE: These parameter do not affect the configuration of Android 12 and later, which
|
||||||
|
# handle splash screens differently that prior versions of Android. Android 12 and later must be
|
||||||
|
# configured specifically in the android_12 section below.
|
||||||
|
|
||||||
|
# color or background_image is the only required parameter. Use color to set the background
|
||||||
|
# of your splash screen to a solid color. Use background_image to set the background of your
|
||||||
|
# splash screen to a png image. This is useful for gradients. The image will be stretch to the
|
||||||
|
# size of the app. Only one parameter can be used, color and background_image cannot both be set.
|
||||||
|
color: "#ffffff"
|
||||||
|
color_dark: "#000000"
|
||||||
|
|
||||||
|
|
||||||
|