Compare commits

...

2 Commits

Author SHA1 Message Date
1e5af07691 improve UX. (#110) 2023-01-25 21:47:05 -08:00
ecf8c902dc bump flutter and linter version. (#108) 2023-01-25 12:33:06 -08:00
41 changed files with 671 additions and 468 deletions

View File

@ -12,12 +12,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
env: env:
FLUTTER_VERSION: "3.3.10" FLUTTER_VERSION: "3.7.0"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.3.10' flutter-version: '3.7.0'
channel: 'stable' channel: 'stable'
- run: flutter pub get - run: flutter pub get
- run: flutter format --set-exit-if-changed . - run: flutter format --set-exit-if-changed .

View File

@ -31,7 +31,7 @@ jobs:
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
cache: true cache: true
flutter-version: 3.3.10 flutter-version: 3.7.0
- run: flutter pub get - run: flutter pub get
- run: flutter format --set-exit-if-changed . - run: flutter format --set-exit-if-changed .
- run: flutter analyze - run: flutter analyze

View File

@ -1,4 +1,4 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml include: package:very_good_analysis/analysis_options.3.1.0.yaml
linter: linter:
rules: rules:
parameter_assignments: false parameter_assignments: false

View File

@ -1,4 +1,4 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml include: package:very_good_analysis/analysis_options.3.1.0.yaml
linter: linter:
rules: rules:
parameter_assignments: false parameter_assignments: false

View File

@ -12,7 +12,7 @@ PODS:
- OrderedSet (~> 5.0) - OrderedSet (~> 5.0)
- flutter_local_notifications (0.0.1): - flutter_local_notifications (0.0.1):
- Flutter - Flutter
- flutter_secure_storage (3.3.1): - flutter_secure_storage (6.0.0):
- Flutter - Flutter
- flutter_siri_suggestions (0.0.1): - flutter_siri_suggestions (0.0.1):
- Flutter - Flutter
@ -24,6 +24,9 @@ PODS:
- OrderedSet (5.0.0) - OrderedSet (5.0.0)
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- path_provider_ios (0.0.1): - path_provider_ios (0.0.1):
- Flutter - Flutter
- ReachabilitySwift (5.0.0) - ReachabilitySwift (5.0.0)
@ -31,6 +34,9 @@ PODS:
- Flutter - Flutter
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_ios (0.0.1): - shared_preferences_ios (0.0.1):
- Flutter - Flutter
- sqflite (0.0.2): - sqflite (0.0.2):
@ -56,9 +62,11 @@ DEPENDENCIES:
- flutter_siri_suggestions (from `.symlinks/plugins/flutter_siri_suggestions/ios`) - flutter_siri_suggestions (from `.symlinks/plugins/flutter_siri_suggestions/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`) - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`)
- synced_shared_preferences (from `.symlinks/plugins/synced_shared_preferences/ios`) - synced_shared_preferences (from `.symlinks/plugins/synced_shared_preferences/ios`)
@ -90,12 +98,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/integration_test/ios" :path: ".symlinks/plugins/integration_test/ios"
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios"
path_provider_ios: path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios" :path: ".symlinks/plugins/path_provider_ios/ios"
receive_sharing_intent: receive_sharing_intent:
:path: ".symlinks/plugins/receive_sharing_intent/ios" :path: ".symlinks/plugins/receive_sharing_intent/ios"
share_plus: share_plus:
:path: ".symlinks/plugins/share_plus/ios" :path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
shared_preferences_ios: shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios" :path: ".symlinks/plugins/shared_preferences_ios/ios"
sqflite: sqflite:
@ -116,20 +128,22 @@ SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
flutter_siri_suggestions: 226fb7ef33d25d3fe0d4aa2a8bcf4b72730c466f flutter_siri_suggestions: 226fb7ef33d25d3fe0d4aa2a8bcf4b72730c466f
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1 receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
synced_shared_preferences: f722742b06d65c7315b8e9f56b794c9fbd5597f7 synced_shared_preferences: f722742b06d65c7315b8e9f56b794c9fbd5597f7
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 51; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -360,6 +360,7 @@
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
@ -413,6 +414,7 @@
}; };
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );

View File

@ -74,5 +74,7 @@
</array> </array>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -84,6 +84,9 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
const StoriesState.init().copyWith( const StoriesState.init().copyWith(
offlineReading: hasCachedStories, offlineReading: hasCachedStories,
currentPageSize: pageSize, currentPageSize: pageSize,
downloadStatus: state.downloadStatus,
storiesDownloaded: state.storiesDownloaded,
storiesToBeDownloaded: state.storiesToBeDownloaded,
), ),
); );
for (final StoryType type in types) { for (final StoryType type in types) {
@ -374,7 +377,6 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
StoriesPageSizeChanged event, StoriesPageSizeChanged event,
Emitter<StoriesState> emit, Emitter<StoriesState> emit,
) async { ) async {
emit(const StoriesState.init());
add(StoriesInitialize()); add(StoriesInitialize());
} }

View File

@ -22,12 +22,12 @@ extension ContextExtension on BuildContext {
static double _screenWidth = 0; static double _screenWidth = 0;
static double _storyTileHeight = 0; static double _storyTileHeight = 0;
static int _storyTileMaxLines = 4; static int _storyTileMaxLines = 4;
static const double _screenWidthLowerBound = 430, static const double _screenWidthLowerBound = 430;
_screenWidthUpperBound = 850, static const double _screenWidthUpperBound = 850;
_picHeightLowerBound = 110, static const double _picHeightLowerBound = 110;
_picHeightUpperBound = 128, static const double _picHeightUpperBound = 128;
_smallPicHeight = 100, static const double _smallPicHeight = 100;
_picHeightFactor = 0.3; static const double _picHeightFactor = 0.3;
double get storyTileHeight { double get storyTileHeight {
final double screenWidth = final double screenWidth =

View File

@ -19,7 +19,7 @@ extension StateExtension on State {
? SnackBarAction( ? SnackBarAction(
label: label, label: label,
onPressed: action, onPressed: action,
textColor: Theme.of(context).textTheme.bodyText1?.color, textColor: Theme.of(context).textTheme.bodyLarge?.color,
) )
: null, : null,
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,

View File

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart';
import 'package:hacki/config/locator.dart'; import 'package:hacki/config/locator.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hacki/repositories/postable_repository.dart'; import 'package:hacki/repositories/postable_repository.dart';
@ -9,13 +8,12 @@ import 'package:logger/logger.dart';
class AuthRepository extends PostableRepository { class AuthRepository extends PostableRepository {
AuthRepository({ AuthRepository({
Dio? dio, super.dio,
PreferenceRepository? preferenceRepository, PreferenceRepository? preferenceRepository,
Logger? logger, Logger? logger,
}) : _preferenceRepository = }) : _preferenceRepository =
preferenceRepository ?? locator.get<PreferenceRepository>(), preferenceRepository ?? locator.get<PreferenceRepository>(),
_logger = logger ?? locator.get<Logger>(), _logger = logger ?? locator.get<Logger>();
super(dio: dio);
final PreferenceRepository _preferenceRepository; final PreferenceRepository _preferenceRepository;
final Logger _logger; final Logger _logger;

View File

@ -8,10 +8,9 @@ import 'package:hacki/repositories/preference_repository.dart';
import 'package:hacki/utils/utils.dart'; import 'package:hacki/utils/utils.dart';
class PostRepository extends PostableRepository { class PostRepository extends PostableRepository {
PostRepository({Dio? dio, PreferenceRepository? storageRepository}) PostRepository({super.dio, PreferenceRepository? storageRepository})
: _preferenceRepository = : _preferenceRepository =
storageRepository ?? locator.get<PreferenceRepository>(), storageRepository ?? locator.get<PreferenceRepository>();
super(dio: dio);
final PreferenceRepository _preferenceRepository; final PreferenceRepository _preferenceRepository;

View File

@ -4,7 +4,7 @@ import 'dart:io';
import 'package:badges/badges.dart'; import 'package:badges/badges.dart';
import 'package:feature_discovery/feature_discovery.dart'; import 'package:feature_discovery/feature_discovery.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' hide Badge;
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -182,7 +182,7 @@ class _HomeScreenState extends State<HomeScreen>
), ),
], ],
), ),
child: Container( child: ColoredBox(
color: Palette.orangeAccent.withOpacity(0.2), color: Palette.orangeAccent.withOpacity(0.2),
child: StoryTile( child: StoryTile(
key: ValueKey<String>('${story.id}-PinnedStoryTile'), key: ValueKey<String>('${story.id}-PinnedStoryTile'),
@ -508,9 +508,8 @@ class _HomeScreenState extends State<HomeScreen>
class _MobileHomeScreen extends StatelessWidget { class _MobileHomeScreen extends StatelessWidget {
const _MobileHomeScreen({ const _MobileHomeScreen({
Key? key,
required this.homeScreen, required this.homeScreen,
}) : super(key: key); });
final Widget homeScreen; final Widget homeScreen;
@ -534,9 +533,8 @@ class _MobileHomeScreen extends StatelessWidget {
class _TabletHomeScreen extends StatelessWidget { class _TabletHomeScreen extends StatelessWidget {
const _TabletHomeScreen({ const _TabletHomeScreen({
Key? key,
required this.homeScreen, required this.homeScreen,
}) : super(key: key); });
final Widget homeScreen; final Widget homeScreen;
@ -561,7 +559,7 @@ class _TabletHomeScreen extends StatelessWidget {
left: Dimens.zero, left: Dimens.zero,
top: Dimens.zero, top: Dimens.zero,
bottom: Dimens.zero, bottom: Dimens.zero,
width: state.expanded ? Dimens.zero : homeScreenWidth, width: homeScreenWidth,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.elasticOut, curve: Curves.elasticOut,
child: homeScreen, child: homeScreen,
@ -592,7 +590,7 @@ class _TabletHomeScreen extends StatelessWidget {
} }
class _TabletStoryView extends StatelessWidget { class _TabletStoryView extends StatelessWidget {
const _TabletStoryView({Key? key}) : super(key: key); const _TabletStoryView();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -605,7 +603,7 @@ class _TabletStoryView extends StatelessWidget {
} }
return Material( return Material(
child: Container( child: ColoredBox(
color: Theme.of(context).canvasColor, color: Theme.of(context).canvasColor,
child: const Center( child: const Center(
child: Text('Tap on story tile to view comments.'), child: Text('Tap on story tile to view comments.'),

View File

@ -7,10 +7,10 @@ import 'package:hacki/styles/styles.dart';
class CustomAppBar extends AppBar { class CustomAppBar extends AppBar {
CustomAppBar({ CustomAppBar({
Key? key, super.key,
required ScrollController scrollController, required ScrollController scrollController,
required Item item, required Item item,
required Color backgroundColor, required Color super.backgroundColor,
required Future<bool> Function() onBackgroundTap, required Future<bool> Function() onBackgroundTap,
required Future<bool> Function() onDismiss, required Future<bool> Function() onDismiss,
required VoidCallback onFontSizeTap, required VoidCallback onFontSizeTap,
@ -19,8 +19,6 @@ class CustomAppBar extends AppBar {
VoidCallback? onZoomTap, VoidCallback? onZoomTap,
bool? expanded, bool? expanded,
}) : super( }) : super(
key: key,
backgroundColor: backgroundColor,
elevation: Dimens.zero, elevation: Dimens.zero,
actions: <Widget>[ actions: <Widget>[
if (splitViewEnabled) ...<Widget>[ if (splitViewEnabled) ...<Widget>[

View File

@ -9,15 +9,15 @@ import 'package:hacki/utils/utils.dart';
class LoginDialog extends StatelessWidget { class LoginDialog extends StatelessWidget {
const LoginDialog({ const LoginDialog({
Key? key, super.key,
required this.usernameController, required this.usernameController,
required this.passwordController, required this.passwordController,
required this.showSnackBar, required this.showSnackBar,
}) : super(key: key); });
final TextEditingController usernameController; final TextEditingController usernameController;
final TextEditingController passwordController; final TextEditingController passwordController;
final Function({ final void Function({
required String content, required String content,
VoidCallback? action, VoidCallback? action,
String? label, String? label,

View File

@ -17,7 +17,7 @@ import 'package:pull_to_refresh/pull_to_refresh.dart';
class MainView extends StatelessWidget { class MainView extends StatelessWidget {
const MainView({ const MainView({
Key? key, super.key,
required this.scrollController, required this.scrollController,
required this.refreshController, required this.refreshController,
required this.commentEditingController, required this.commentEditingController,
@ -29,7 +29,7 @@ class MainView extends StatelessWidget {
required this.onStoryLinkTapped, required this.onStoryLinkTapped,
required this.onLoginTapped, required this.onLoginTapped,
required this.onRightMoreTapped, required this.onRightMoreTapped,
}) : super(key: key); });
final ScrollController scrollController; final ScrollController scrollController;
final RefreshController refreshController; final RefreshController refreshController;
@ -38,11 +38,14 @@ class MainView extends StatelessWidget {
final FocusNode focusNode; final FocusNode focusNode;
final double topPadding; final double topPadding;
final bool splitViewEnabled; final bool splitViewEnabled;
final Function(Item item, Rect? rect) onMoreTapped; final void Function(Item item, Rect? rect) onMoreTapped;
final ValueChanged<String> onStoryLinkTapped; final ValueChanged<String> onStoryLinkTapped;
final VoidCallback onLoginTapped; final VoidCallback onLoginTapped;
final ValueChanged<Comment> onRightMoreTapped; final ValueChanged<Comment> onRightMoreTapped;
static const int _loadingIndicatorOpacityAnimationDuration = 300;
static const double _trailingBoxHeight = 240;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
@ -130,7 +133,7 @@ class MainView extends StatelessWidget {
state.comments.isNotEmpty) || state.comments.isNotEmpty) ||
state.onlyShowTargetComment) { state.onlyShowTargetComment) {
return SizedBox( return SizedBox(
height: 240, height: _trailingBoxHeight,
child: Center( child: Center(
child: Text(Constants.happyFaces.pickRandomly()!), child: Text(Constants.happyFaces.pickRandomly()!),
), ),
@ -194,8 +197,13 @@ class MainView extends StatelessWidget {
buildWhen: (CommentsState prev, CommentsState current) => buildWhen: (CommentsState prev, CommentsState current) =>
prev.status != current.status, prev.status != current.status,
builder: (BuildContext context, CommentsState state) { builder: (BuildContext context, CommentsState state) {
return Visibility( return AnimatedOpacity(
visible: state.status == CommentsStatus.loading, opacity: state.status == CommentsStatus.loading
? NumSwitch.on
: NumSwitch.off,
duration: const Duration(
milliseconds: _loadingIndicatorOpacityAnimationDuration,
),
child: const LinearProgressIndicator(), child: const LinearProgressIndicator(),
); );
}, },
@ -208,7 +216,6 @@ class MainView extends StatelessWidget {
class _ParentItemSection extends StatelessWidget { class _ParentItemSection extends StatelessWidget {
const _ParentItemSection({ const _ParentItemSection({
Key? key,
required this.scrollController, required this.scrollController,
required this.refreshController, required this.refreshController,
required this.commentEditingController, required this.commentEditingController,
@ -221,7 +228,7 @@ class _ParentItemSection extends StatelessWidget {
required this.onStoryLinkTapped, required this.onStoryLinkTapped,
required this.onLoginTapped, required this.onLoginTapped,
required this.onRightMoreTapped, required this.onRightMoreTapped,
}) : super(key: key); });
final ScrollController scrollController; final ScrollController scrollController;
final RefreshController refreshController; final RefreshController refreshController;
@ -231,7 +238,7 @@ class _ParentItemSection extends StatelessWidget {
final FocusNode focusNode; final FocusNode focusNode;
final double topPadding; final double topPadding;
final bool splitViewEnabled; final bool splitViewEnabled;
final Function(Item item, Rect? rect) onMoreTapped; final void Function(Item item, Rect? rect) onMoreTapped;
final ValueChanged<String> onStoryLinkTapped; final ValueChanged<String> onStoryLinkTapped;
final VoidCallback onLoginTapped; final VoidCallback onLoginTapped;
final ValueChanged<Comment> onRightMoreTapped; final ValueChanged<Comment> onRightMoreTapped;
@ -342,7 +349,7 @@ class _ParentItemSection extends StatelessWidget {
prefState.fontSize.fontSize, prefState.fontSize.fontSize,
color: Theme.of(context) color: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyLarge
?.color, ?.color,
), ),
children: <TextSpan>[ children: <TextSpan>[

View File

@ -12,17 +12,17 @@ import 'package:hacki/utils/utils.dart';
class MorePopupMenu extends StatelessWidget { class MorePopupMenu extends StatelessWidget {
const MorePopupMenu({ const MorePopupMenu({
Key? key, super.key,
required this.item, required this.item,
required this.isBlocked, required this.isBlocked,
required this.showSnackBar, required this.showSnackBar,
required this.onStoryLinkTapped, required this.onStoryLinkTapped,
required this.onLoginTapped, required this.onLoginTapped,
}) : super(key: key); });
final Item item; final Item item;
final bool isBlocked; final bool isBlocked;
final Function({ final void Function({
required String content, required String content,
VoidCallback? action, VoidCallback? action,
String? label, String? label,

View File

@ -168,7 +168,7 @@ class PollView extends StatelessWidget {
? SnackBarAction( ? SnackBarAction(
label: label, label: label,
onPressed: action, onPressed: action,
textColor: Theme.of(context).textTheme.bodyText1?.color, textColor: Theme.of(context).textTheme.bodyLarge?.color,
) )
: null, : null,
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,

View File

@ -111,7 +111,8 @@ class _ReplyBoxState extends State<ReplyBox> {
...<Widget>[ ...<Widget>[
if (replyingTo != null) if (replyingTo != null)
AnimatedOpacity( AnimatedOpacity(
opacity: expanded ? 1 : 0, opacity:
expanded ? NumSwitch.on : NumSwitch.off,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: IconButton( child: IconButton(
key: const Key('quote'), key: const Key('quote'),

View File

@ -9,19 +9,19 @@ import 'package:responsive_builder/responsive_builder.dart';
class TimeMachineDialog extends StatelessWidget { class TimeMachineDialog extends StatelessWidget {
const TimeMachineDialog({ const TimeMachineDialog({
Key? key, super.key,
required this.comment, required this.comment,
required this.size, required this.size,
required this.deviceType, required this.deviceType,
required this.widthFactor, required this.widthFactor,
required this.onStoryLinkTapped, required this.onStoryLinkTapped,
}) : super(key: key); });
final Comment comment; final Comment comment;
final Size size; final Size size;
final DeviceScreenType deviceType; final DeviceScreenType deviceType;
final double widthFactor; final double widthFactor;
final Function(String) onStoryLinkTapped; final void Function(String) onStoryLinkTapped;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -650,89 +650,93 @@ class _ProfileScreenState extends State<ProfileScreen>
final PackageInfo packageInfo = await PackageInfo.fromPlatform(); final PackageInfo packageInfo = await PackageInfo.fromPlatform();
final String version = packageInfo.version; final String version = packageInfo.version;
showAboutDialog( if (mounted) {
context: context, showAboutDialog(
applicationName: 'Hacki', context: context,
applicationVersion: 'v$version', applicationName: 'Hacki',
applicationIcon: ClipRRect( applicationVersion: 'v$version',
borderRadius: const BorderRadius.all( applicationIcon: ClipRRect(
Radius.circular( borderRadius: const BorderRadius.all(
Dimens.pt12, Radius.circular(
Dimens.pt12,
),
),
child: Image.asset(
Constants.hackiIconPath,
height: Dimens.pt50,
width: Dimens.pt50,
), ),
), ),
child: Image.asset( children: <Widget>[
Constants.hackiIconPath, ElevatedButton(
height: Dimens.pt50, onPressed: () => LinkUtil.launch(
width: Dimens.pt50, Constants.portfolioLink,
), ),
), child: Row(
children: <Widget>[ children: const <Widget>[
ElevatedButton( Icon(
onPressed: () => LinkUtil.launch( FontAwesomeIcons.addressCard,
Constants.portfolioLink, ),
SizedBox(
width: Dimens.pt12,
),
Text('Developer'),
],
),
), ),
child: Row( ElevatedButton(
children: const <Widget>[ onPressed: () => LinkUtil.launch(
Icon( Constants.githubLink,
FontAwesomeIcons.addressCard, ),
), child: Row(
SizedBox( children: const <Widget>[
width: Dimens.pt12, Icon(
), FontAwesomeIcons.github,
Text('Developer'), ),
], SizedBox(
width: Dimens.pt12,
),
Text('Source code'),
],
),
), ),
), ElevatedButton(
ElevatedButton( onPressed: () => LinkUtil.launch(
onPressed: () => LinkUtil.launch( Platform.isIOS
Constants.githubLink, ? Constants.appStoreLink
: Constants.googlePlayLink,
),
child: Row(
children: const <Widget>[
Icon(
Icons.thumb_up,
),
SizedBox(
width: Dimens.pt12,
),
Text('Like the app?'),
],
),
), ),
child: Row( ElevatedButton(
children: const <Widget>[ onPressed: () => LinkUtil.launch(
Icon( Constants.sponsorLink,
FontAwesomeIcons.github, ),
), child: Row(
SizedBox( children: const <Widget>[
width: Dimens.pt12, Icon(
), FeatherIcons.coffee,
Text('Source code'), ),
], SizedBox(
width: Dimens.pt12,
),
Text('Buy me a coffee'),
],
),
), ),
), ],
ElevatedButton( );
onPressed: () => LinkUtil.launch( }
Platform.isIOS ? Constants.appStoreLink : Constants.googlePlayLink,
),
child: Row(
children: const <Widget>[
Icon(
Icons.thumb_up,
),
SizedBox(
width: Dimens.pt12,
),
Text('Like the app?'),
],
),
),
ElevatedButton(
onPressed: () => LinkUtil.launch(
Constants.sponsorLink,
),
child: Row(
children: const <Widget>[
Icon(
FeatherIcons.coffee,
),
SizedBox(
width: Dimens.pt12,
),
Text('Buy me a coffee'),
],
),
),
],
);
} }
void onCommentTapped(Comment comment, {VoidCallback? then}) { void onCommentTapped(Comment comment, {VoidCallback? then}) {

View File

@ -22,7 +22,7 @@ class InboxView extends StatelessWidget {
final RefreshController refreshController; final RefreshController refreshController;
final List<Comment> comments; final List<Comment> comments;
final List<int> unreadCommentsIds; final List<int> unreadCommentsIds;
final Function(Comment) onCommentTapped; final void Function(Comment) onCommentTapped;
final VoidCallback onMarkAllAsReadTapped; final VoidCallback onMarkAllAsReadTapped;
final VoidCallback onLoadMore; final VoidCallback onLoadMore;
final VoidCallback onRefresh; final VoidCallback onRefresh;

View File

@ -179,8 +179,7 @@ class _SearchScreenState extends State<SearchScreen> {
), ),
], ],
) )
.expand((List<Widget> e) => e) .expand((List<Widget> e) => e),
.toList(),
const SizedBox( const SizedBox(
height: Dimens.pt40, height: Dimens.pt40,
), ),

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hacki/screens/widgets/widgets.dart'; import 'package:hacki/screens/widgets/widgets.dart';
typedef DateRangeCallback = Function(DateTime, DateTime); typedef DateRangeCallback = void Function(DateTime, DateTime);
enum CustomDateTimeRange { enum CustomDateTimeRange {
pastDay(Duration(days: 1), label: 'past day'), pastDay(Duration(days: 1), label: 'past day'),
@ -17,10 +17,10 @@ enum CustomDateTimeRange {
class CustomRangeFilterChip extends StatelessWidget { class CustomRangeFilterChip extends StatelessWidget {
const CustomRangeFilterChip({ const CustomRangeFilterChip({
Key? key, super.key,
required this.range, required this.range,
required this.onTap, required this.onTap,
}) : super(key: key); });
final CustomDateTimeRange range; final CustomDateTimeRange range;
final DateRangeCallback onTap; final DateRangeCallback onTap;

View File

@ -5,14 +5,14 @@ import 'package:intl/intl.dart';
class DateTimeRangeFilterChip extends StatelessWidget { class DateTimeRangeFilterChip extends StatelessWidget {
const DateTimeRangeFilterChip({ const DateTimeRangeFilterChip({
Key? key, super.key,
required this.filter, required this.filter,
required this.onDateTimeRangeUpdated, required this.onDateTimeRangeUpdated,
required this.onDateTimeRangeRemoved, required this.onDateTimeRangeRemoved,
}) : super(key: key); });
final DateTimeRangeFilter? filter; final DateTimeRangeFilter? filter;
final Function(DateTime, DateTime) onDateTimeRangeUpdated; final void Function(DateTime, DateTime) onDateTimeRangeUpdated;
final VoidCallback onDateTimeRangeRemoved; final VoidCallback onDateTimeRangeRemoved;
static final DateFormat _dateTimeFormatter = DateFormat.yMMMd(); static final DateFormat _dateTimeFormatter = DateFormat.yMMMd();

View File

@ -4,9 +4,9 @@ import 'package:hacki/screens/widgets/widgets.dart';
class PostedByFilterChip extends StatelessWidget { class PostedByFilterChip extends StatelessWidget {
const PostedByFilterChip({ const PostedByFilterChip({
Key? key, super.key,
required this.filter, required this.filter,
}) : super(key: key); });
final PostedByFilter? filter; final PostedByFilter? filter;

View File

@ -17,7 +17,7 @@ class BlocBuilder3<
BlocC extends StateStreamable<BlocCState>, BlocC extends StateStreamable<BlocCState>,
BlocCState> extends StatelessWidget { BlocCState> extends StatelessWidget {
const BlocBuilder3({ const BlocBuilder3({
Key? key, super.key,
required this.builder, required this.builder,
this.blocA, this.blocA,
this.blocB, this.blocB,
@ -25,7 +25,7 @@ class BlocBuilder3<
this.buildWhenA, this.buildWhenA,
this.buildWhenB, this.buildWhenB,
this.buildWhenC, this.buildWhenC,
}) : super(key: key); });
final BlocWidgetBuilder3<BlocAState, BlocBState, BlocCState> builder; final BlocWidgetBuilder3<BlocAState, BlocBState, BlocCState> builder;

View File

@ -33,11 +33,11 @@ class CommentTile extends StatelessWidget {
final Comment comment; final Comment comment;
final int level; final int level;
final bool actionable; final bool actionable;
final Function(Comment)? onReplyTapped; final void Function(Comment)? onReplyTapped;
final Function(Comment, Rect?)? onMoreTapped; final void Function(Comment, Rect?)? onMoreTapped;
final Function(Comment)? onEditTapped; final void Function(Comment)? onEditTapped;
final Function(Comment)? onRightMoreTapped; final void Function(Comment)? onRightMoreTapped;
final Function(String) onStoryLinkTapped; final void Function(String) onStoryLinkTapped;
final FetchMode fetchMode; final FetchMode fetchMode;
@override @override

View File

@ -57,7 +57,7 @@ class ItemsListView<T extends Item> extends StatelessWidget {
final VoidCallback? onRefresh; final VoidCallback? onRefresh;
final VoidCallback? onLoadMore; final VoidCallback? onLoadMore;
final ValueChanged<Story>? onPinned; final ValueChanged<Story>? onPinned;
final Function(T) onTap; final void Function(T) onTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -249,11 +249,10 @@ class ItemsListView<T extends Item> extends StatelessWidget {
class _CommentTile extends StatelessWidget { class _CommentTile extends StatelessWidget {
const _CommentTile({ const _CommentTile({
Key? key,
required this.comment, required this.comment,
required this.onTap, required this.onTap,
this.fontSize = 16, this.fontSize = 16,
}) : super(key: key); });
final Comment comment; final Comment comment;
final VoidCallback onTap; final VoidCallback onTap;

View File

@ -113,7 +113,8 @@ class LinkPreview extends StatefulWidget {
class _LinkPreviewState extends State<LinkPreview> { class _LinkPreviewState extends State<LinkPreview> {
InfoBase? _info; InfoBase? _info;
String? _errorTitle, _errorBody; String? _errorTitle;
String? _errorBody;
bool _loading = false; bool _loading = false;
@override @override
@ -158,7 +159,7 @@ class _LinkPreviewState extends State<LinkPreview> {
} }
Widget _buildLinkContainer( Widget _buildLinkContainer(
double _height, { double height, {
String? title = '', String? title = '',
String? desc = '', String? desc = '',
String? imageUri = '', String? imageUri = '',
@ -177,7 +178,7 @@ class _LinkPreviewState extends State<LinkPreview> {
const BoxShadow(blurRadius: 3, color: Palette.grey), const BoxShadow(blurRadius: 3, color: Palette.grey),
], ],
), ),
height: _height, height: height,
child: LinkView( child: LinkView(
key: widget.key ?? Key(widget.link), key: widget.key ?? Key(widget.link),
metadata: widget.story.simpleMetadata, metadata: widget.story.simpleMetadata,

View File

@ -37,7 +37,7 @@ class LinkView extends StatelessWidget {
final String description; final String description;
final String? imageUri; final String? imageUri;
final String? imagePath; final String? imagePath;
final Function(String) onTap; final void Function(String) onTap;
final TextStyle? titleTextStyle; final TextStyle? titleTextStyle;
final TextStyle? bodyTextStyle; final TextStyle? bodyTextStyle;
final bool showMultiMedia; final bool showMultiMedia;
@ -76,13 +76,13 @@ class LinkView extends StatelessWidget {
final double layoutWidth = constraints.biggest.width; final double layoutWidth = constraints.biggest.width;
final double layoutHeight = constraints.biggest.height; final double layoutHeight = constraints.biggest.height;
final TextStyle _titleFontSize = titleTextStyle ?? final TextStyle titleFontSize = titleTextStyle ??
TextStyle( TextStyle(
fontSize: computeTitleFontSize(layoutWidth), fontSize: computeTitleFontSize(layoutWidth),
color: Palette.black, color: Palette.black,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
); );
final TextStyle _bodyFontSize = bodyTextStyle ?? final TextStyle bodyFontSize = bodyTextStyle ??
TextStyle( TextStyle(
fontSize: computeTitleFontSize(layoutWidth) - 1, fontSize: computeTitleFontSize(layoutWidth) - 1,
color: Palette.grey, color: Palette.grey,
@ -131,11 +131,11 @@ class LinkView extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
_buildTitleContainer( _buildTitleContainer(
_titleFontSize, titleFontSize,
computeTitleLines(layoutHeight), computeTitleLines(layoutHeight),
), ),
_buildBodyContainer( _buildBodyContainer(
_bodyFontSize, bodyFontSize,
computeBodyLines(layoutHeight), computeBodyLines(layoutHeight),
) )
], ],
@ -149,7 +149,7 @@ class LinkView extends StatelessWidget {
); );
} }
Widget _buildTitleContainer(TextStyle _titleTS, int _maxLines) { Widget _buildTitleContainer(TextStyle titleTS, int maxLines) {
final bool showUrl = this.showUrl && url.isNotEmpty; final bool showUrl = this.showUrl && url.isNotEmpty;
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(4, 2, 3, 0), padding: const EdgeInsets.fromLTRB(4, 2, 3, 0),
@ -159,9 +159,9 @@ class LinkView extends StatelessWidget {
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Text( child: Text(
title, title,
style: _titleTS, style: titleTS,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: _maxLines, maxLines: maxLines,
), ),
), ),
if (showUrl) if (showUrl)
@ -170,10 +170,10 @@ class LinkView extends StatelessWidget {
child: Text( child: Text(
'($readableUrl)', '($readableUrl)',
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: _titleTS.copyWith( style: titleTS.copyWith(
color: Palette.grey, color: Palette.grey,
fontSize: fontSize:
_titleTS.fontSize == null ? 12 : _titleTS.fontSize! - 4, titleTS.fontSize == null ? 12 : titleTS.fontSize! - 4,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
overflow: bodyTextOverflow ?? TextOverflow.ellipsis, overflow: bodyTextOverflow ?? TextOverflow.ellipsis,
@ -185,7 +185,7 @@ class LinkView extends StatelessWidget {
); );
} }
Widget _buildBodyContainer(TextStyle _bodyTS, int _maxLines) { Widget _buildBodyContainer(TextStyle bodyTS, int maxLines) {
return Expanded( return Expanded(
flex: 2, flex: 2,
child: Padding( child: Padding(
@ -198,9 +198,9 @@ class LinkView extends StatelessWidget {
child: Text( child: Text(
metadata, metadata,
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: _bodyTS.copyWith( style: bodyTS.copyWith(
fontSize: fontSize:
_bodyTS.fontSize == null ? 12 : _bodyTS.fontSize! - 2, bodyTS.fontSize == null ? 12 : bodyTS.fontSize! - 2,
), ),
overflow: bodyTextOverflow ?? TextOverflow.ellipsis, overflow: bodyTextOverflow ?? TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,
@ -212,9 +212,9 @@ class LinkView extends StatelessWidget {
child: Text( child: Text(
description, description,
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: _bodyTS, style: bodyTS,
overflow: bodyTextOverflow ?? TextOverflow.ellipsis, overflow: bodyTextOverflow ?? TextOverflow.ellipsis,
maxLines: (bodyMaxLines ?? _maxLines) - maxLines: (bodyMaxLines ?? maxLines) -
(showMetadata ? 1 : 0) - (showMetadata ? 1 : 0) -
(showUrl && url.isNotEmpty ? 1 : 0), (showUrl && url.isNotEmpty ? 1 : 0),
), ),

View File

@ -63,7 +63,7 @@ class WebImageInfo extends InfoBase {
/// Video Information /// Video Information
class WebVideoInfo extends WebImageInfo { class WebVideoInfo extends WebImageInfo {
WebVideoInfo({String? image}) : super(image: image); WebVideoInfo({super.image});
} }
/// Web analyzer /// Web analyzer

View File

@ -6,7 +6,7 @@ import 'package:hacki/styles/styles.dart';
import 'package:hacki/utils/utils.dart'; import 'package:hacki/utils/utils.dart';
class OnboardingView extends StatefulWidget { class OnboardingView extends StatefulWidget {
const OnboardingView({Key? key}) : super(key: key); const OnboardingView({super.key});
@override @override
State<OnboardingView> createState() => _OnboardingViewState(); State<OnboardingView> createState() => _OnboardingViewState();
@ -106,10 +106,9 @@ class _OnboardingViewState extends State<OnboardingView> {
class _PageViewChild extends StatelessWidget { class _PageViewChild extends StatelessWidget {
const _PageViewChild({ const _PageViewChild({
Key? key,
required this.path, required this.path,
required this.description, required this.description,
}) : super(key: key); });
final String path; final String path;
final String description; final String description;

View File

@ -56,7 +56,7 @@ class StoryTile extends StatelessWidget {
titleStyle: TextStyle( titleStyle: TextStyle(
color: hasRead color: hasRead
? Palette.grey[500] ? Palette.grey[500]
: Theme.of(context).textTheme.subtitle1?.color, : Theme.of(context).textTheme.bodyLarge?.color,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
showMetadata: showMetadata, showMetadata: showMetadata,
@ -90,7 +90,7 @@ class StoryTile extends StatelessWidget {
? Palette.grey[500] ? Palette.grey[500]
: Theme.of(context) : Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyLarge
?.color, ?.color,
fontSize: simpleTileFontSize, fontSize: simpleTileFontSize,
), ),
@ -137,9 +137,8 @@ class StoryTile extends StatelessWidget {
class _LinkPreviewPlaceholder extends StatelessWidget { class _LinkPreviewPlaceholder extends StatelessWidget {
const _LinkPreviewPlaceholder({ const _LinkPreviewPlaceholder({
Key? key,
required this.height, required this.height,
}) : super(key: key); });
final double height; final double height;

View File

@ -6,7 +6,6 @@ export 'custom_chip.dart';
export 'custom_circular_progress_indicator.dart'; export 'custom_circular_progress_indicator.dart';
export 'items_list_view.dart'; export 'items_list_view.dart';
export 'link_preview/link_preview.dart'; export 'link_preview/link_preview.dart';
export 'link_preview/link_preview.dart';
export 'offline_banner.dart'; export 'offline_banner.dart';
export 'onboarding_view.dart'; export 'onboarding_view.dart';
export 'spring_curve.dart'; export 'spring_curve.dart';

View File

@ -38,3 +38,8 @@ abstract class TextDimens {
static const double pt26 = 26; static const double pt26 = 26;
static const double pt36 = 36; static const double pt36 = 36;
} }
abstract class NumSwitch {
static const double on = 1;
static const double off = 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
name: hacki name: hacki
description: A Hacker News reader. description: A Hacker News reader.
version: 1.0.2+80 version: 1.0.3+81
publish_to: none publish_to: none
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"
flutter: "3.3.10" flutter: "3.7.0"
dependencies: dependencies:
adaptive_theme: ^3.0.0 adaptive_theme: ^3.0.0
@ -73,12 +73,14 @@ dependencies:
dev_dependencies: dev_dependencies:
bloc_test: ^9.1.0 bloc_test: ^9.1.0
flutter_driver:
sdk: flutter
flutter_test: flutter_test:
sdk: flutter sdk: flutter
integration_test: integration_test:
sdk: flutter sdk: flutter
mocktail: ^0.3.0 mocktail: ^0.3.0
very_good_analysis: ^2.4.0 very_good_analysis: ^3.1.0
flutter: flutter:
uses-material-design: true uses-material-design: true

View File

@ -20,8 +20,11 @@ void main() {
final MockStoriesRepository mockStoriesRepository = MockStoriesRepository(); final MockStoriesRepository mockStoriesRepository = MockStoriesRepository();
final MockSembastRepository mockSembastRepository = MockSembastRepository(); final MockSembastRepository mockSembastRepository = MockSembastRepository();
const int created = 0, delay = 1, karma = 2; const int created = 0;
const String about = 'about', id = 'id'; const int delay = 1;
const int karma = 2;
const String about = 'about';
const String id = 'id';
const User tUser = User( const User tUser = User(
about: about, about: about,
@ -57,7 +60,8 @@ void main() {
); );
group('AuthAppStarted', () { group('AuthAppStarted', () {
const String username = 'username', password = 'password'; const String username = 'username';
const String password = 'password';
setUp(() { setUp(() {
when(() => mockAuthRepository.username) when(() => mockAuthRepository.username)
.thenAnswer((_) => Future<String?>.value(username)); .thenAnswer((_) => Future<String?>.value(username));