Compare commits

...

11 Commits

Author SHA1 Message Date
fe162208ca fix expand animation. (#142) 2023-02-10 14:08:31 -08:00
58139ba7a3 update commit_check.yml (#141) 2023-02-09 15:42:08 -08:00
33a31acbe2 update Fastfile. 2023-02-09 15:20:19 -08:00
0fcfcbb7e3 update Fastfile. (#140) 2023-02-09 15:12:00 -08:00
a98f52c90b update publish_ios.yml 2023-02-09 14:37:11 -08:00
8e8e48c44a update GitHub action. (#139) 2023-02-09 14:28:46 -08:00
603b7cc939 bump flutter to 3.7.3 (#138) 2023-02-09 11:27:03 -08:00
649fa33df3 fix err msg. (#137) 2023-02-09 00:19:34 -08:00
81d4a0f2df banner cleanup. (#136) 2023-02-08 23:44:15 -08:00
24112a471e add collapse/expand animation to comment tile. (#135) 2023-02-08 23:08:09 -08:00
c7824eaef3 bump flutter to 3.7.2 (#134) 2023-02-08 17:43:23 -08:00
30 changed files with 286 additions and 288 deletions

View File

@ -11,15 +11,13 @@ jobs:
name: Check commit
runs-on: ubuntu-latest
timeout-minutes: 30
env:
FLUTTER_VERSION: "3.7.1"
steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v2
- name: checkout all the submodules
uses: actions/checkout@v3
with:
flutter-version: '3.7.1'
channel: 'stable'
- run: flutter pub get
- run: flutter format --set-exit-if-changed .
- run: flutter analyze
- run: flutter test
submodules: recursive
- run: submodules/flutter/bin/flutter doctor
- run: submodules/flutter/bin/flutter pub get
- run: submodules/flutter/bin/dart format --set-exit-if-changed lib test integration_test
- run: submodules/flutter/bin/flutter analyze lib test integration_test
- run: submodules/flutter/bin/flutter test

View File

@ -20,21 +20,21 @@ jobs:
steps:
- name: Check out from git
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
submodules: recursive
- run: submodules/flutter/bin/flutter doctor
- run: submodules/flutter/bin/flutter pub get
- run: submodules/flutter/bin/dart format --set-exit-if-changed lib test integration_test
- run: submodules/flutter/bin/flutter analyze lib test integration_test
- run: submodules/flutter/bin/flutter test
# Configure ruby according to our .ruby-version
- name: Setup ruby & Bundler
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
# Set up flutter (feel free to adjust the version below)
- name: Setup flutter
uses: subosito/flutter-action@v2
with:
cache: true
flutter-version: 3.7.1
- run: flutter pub get
- run: flutter format --set-exit-if-changed .
- run: flutter analyze
# Start an ssh-agent that will provide the SSH key from the
# SSH_PRIVATE_KEY secret to `fastlane match`
- name: Setup SSH key
@ -43,8 +43,7 @@ jobs:
run: |
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}"
- name: Download dependencies
run: flutter pub get
- name: Build & Publish to TestFlight with Fastlane
env:
APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}

View File

@ -49,7 +49,7 @@ latest_testflight_build_number
# Prep the xcodeproject from Flutter without building (`--config-only`)
sh(
"flutter", "build", "ios", "--config-only",
"/Users/runner/work/Hacki/Hacki/submodules/flutter/bin/flutter", "build", "ios", "--config-only",
"--release", "--no-pub", "--no-codesign",
"--build-number", new_build_number.to_s
)

View File

@ -56,6 +56,8 @@ abstract class Constants {
'ʕ•́ᴥ•̀ʔっ',
'(ㆆ_ㆆ)',
].pickRandomly()!;
static final String errorMessage = 'Something went wrong...$sadFace';
}
abstract class RegExpConstants {

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/screens/screens.dart';
/// Custom router.
@ -39,8 +40,8 @@ class CustomRouter {
appBar: AppBar(
title: const Text('Error'),
),
body: const Center(
child: Text('Something went wrong!'),
body: Center(
child: Text(Constants.errorMessage),
),
),
);

View File

@ -20,7 +20,7 @@ Future<void> setUpLocator() async {
Logger(
filter: CustomLogFilter(),
printer: LogUtil.logPrinter,
output: LogUtil.getLogOutput(logOutputFile),
output: LogUtil.logOutput(logOutputFile),
),
)
..registerSingleton<StoriesRepository>(StoriesRepository())

View File

@ -2,6 +2,8 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/styles/styles.dart';
extension ContextExtension on BuildContext {
T? tryRead<T>() {
@ -12,6 +14,31 @@ extension ContextExtension on BuildContext {
}
}
void showSnackBar({
required String content,
VoidCallback? action,
String? label,
}) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
backgroundColor: Palette.deepOrange,
content: Text(content),
action: action != null && label != null
? SnackBarAction(
label: label,
onPressed: action,
textColor: Theme.of(this).textTheme.bodyLarge?.color,
)
: null,
behavior: SnackBarBehavior.floating,
),
);
}
void showErrorSnackBar() => showSnackBar(
content: Constants.errorMessage,
);
Rect? get rect {
final RenderBox? box = findRenderObject() as RenderBox?;
final Rect? rect =

View File

@ -21,22 +21,15 @@ extension StateExtension on State {
VoidCallback? action,
String? label,
}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Palette.deepOrange,
content: Text(content),
action: action != null && label != null
? SnackBarAction(
label: label,
onPressed: action,
textColor: Theme.of(context).textTheme.bodyLarge?.color,
)
: null,
behavior: SnackBarBehavior.floating,
),
context.showSnackBar(
content: content,
action: action,
label: label,
);
}
void showErrorSnackBar() => context.showErrorSnackBar();
Future<void>? goToItemScreen({
required ItemScreenArgs args,
bool forceNewScreen = false,
@ -70,7 +63,6 @@ extension StateExtension on State {
return MorePopupMenu(
item: item,
isBlocked: isBlocked,
showSnackBar: showSnackBar,
onStoryLinkTapped: onStoryLinkTapped,
onLoginTapped: onLoginTapped,
);

View File

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:hacki/config/locator.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/repositories/postable_repository.dart';
import 'package:hacki/repositories/repositories.dart';
import 'package:hacki/repositories/preference_repository.dart';
import 'package:logger/logger.dart';
class AuthRepository extends PostableRepository {
@ -18,8 +18,6 @@ class AuthRepository extends PostableRepository {
final PreferenceRepository _preferenceRepository;
final Logger _logger;
static const String _authority = 'news.ycombinator.com';
Future<bool> get loggedIn async => _preferenceRepository.loggedIn;
Future<String?> get username async => _preferenceRepository.username;
@ -30,7 +28,7 @@ class AuthRepository extends PostableRepository {
required String username,
required String password,
}) async {
final Uri uri = Uri.https(_authority, 'login');
final Uri uri = Uri.https(authority, 'login');
final PostDataMixin data = LoginPostData(
acct: username,
pw: password,
@ -64,7 +62,7 @@ class AuthRepository extends PostableRepository {
required int id,
required bool flag,
}) async {
final Uri uri = Uri.https(_authority, 'flag');
final Uri uri = Uri.https(authority, 'flag');
final String? username = await _preferenceRepository.username;
final String? password = await _preferenceRepository.password;
final PostDataMixin data = FlagPostData(
@ -81,7 +79,7 @@ class AuthRepository extends PostableRepository {
required int id,
required bool favorite,
}) async {
final Uri uri = Uri.https(_authority, 'fave');
final Uri uri = Uri.https(authority, 'fave');
final String? username = await _preferenceRepository.username;
final String? password = await _preferenceRepository.password;
final PostDataMixin data = FavoritePostData(
@ -98,7 +96,7 @@ class AuthRepository extends PostableRepository {
required int id,
required bool upvote,
}) async {
final Uri uri = Uri.https(_authority, 'vote');
final Uri uri = Uri.https(authority, 'vote');
final String? username = await _preferenceRepository.username;
final String? password = await _preferenceRepository.password;
final PostDataMixin data = VotePostData(
@ -115,7 +113,7 @@ class AuthRepository extends PostableRepository {
required int id,
required bool downvote,
}) async {
final Uri uri = Uri.https(_authority, 'vote');
final Uri uri = Uri.https(authority, 'vote');
final String? username = await _preferenceRepository.username;
final String? password = await _preferenceRepository.password;
final PostDataMixin data = VotePostData(

View File

@ -14,15 +14,13 @@ class PostRepository extends PostableRepository {
final PreferenceRepository _preferenceRepository;
static const String _authority = 'news.ycombinator.com';
Future<bool> comment({
required int parentId,
required String text,
}) async {
final String? username = await _preferenceRepository.username;
final String? password = await _preferenceRepository.password;
final Uri uri = Uri.https(_authority, 'comment');
final Uri uri = Uri.https(authority, 'comment');
if (username == null || password == null) {
return false;
@ -54,7 +52,7 @@ class PostRepository extends PostableRepository {
return false;
}
final Response<List<int>> formResponse = await _getFormResponse(
final Response<List<int>> formResponse = await getFormResponse(
username: username,
password: password,
path: 'submitlink',
@ -69,7 +67,7 @@ class PostRepository extends PostableRepository {
final String? cookie =
formResponse.headers.value(HttpHeaders.setCookieHeader);
final Uri uri = Uri.https(_authority, 'r');
final Uri uri = Uri.https(authority, 'r');
final PostDataMixin data = SubmitPostData(
fnid: formValues['fnid']!,
fnop: formValues['fnop']!,
@ -97,7 +95,7 @@ class PostRepository extends PostableRepository {
return false;
}
final Response<List<int>> formResponse = await _getFormResponse(
final Response<List<int>> formResponse = await getFormResponse(
username: username,
password: password,
id: id,
@ -113,7 +111,7 @@ class PostRepository extends PostableRepository {
final String? cookie =
formResponse.headers.value(HttpHeaders.setCookieHeader);
final Uri uri = Uri.https(_authority, 'xedit');
final Uri uri = Uri.https(authority, 'xedit');
final PostDataMixin data = EditPostData(
hmac: formValues['hmac']!,
id: id,
@ -126,28 +124,4 @@ class PostRepository extends PostableRepository {
cookie: cookie,
);
}
Future<Response<List<int>>> _getFormResponse({
required String username,
required String password,
required String path,
int? id,
}) async {
final Uri uri = Uri.https(
_authority,
path,
<String, dynamic>{if (id != null) 'id': id.toString()},
);
final PostDataMixin data = FormPostData(
acct: username,
pw: password,
id: id,
);
return performPost(
uri,
data,
responseType: ResponseType.bytes,
validateStatus: (int? status) => status == HttpStatus.ok,
);
}
}

View File

@ -8,10 +8,14 @@ import 'package:hacki/utils/service_exception.dart';
class PostableRepository {
PostableRepository({
Dio? dio,
this.authority = 'news.ycombinator.com',
}) : _dio = dio ?? Dio();
final Dio _dio;
@protected
final String authority;
@protected
Future<bool> performDefaultPost(
Uri uri,
@ -60,4 +64,29 @@ class PostableRepository {
throw ServiceException(e.message);
}
}
@protected
Future<Response<List<int>>> getFormResponse({
required String username,
required String password,
required String path,
int? id,
}) async {
final Uri uri = Uri.https(
authority,
path,
<String, dynamic>{if (id != null) 'id': id.toString()},
);
final PostDataMixin data = FormPostData(
acct: username,
pw: password,
id: id,
);
return performPost(
uri,
data,
responseType: ResponseType.bytes,
validateStatus: (int? status) => status == HttpStatus.ok,
);
}
}

View File

@ -301,7 +301,7 @@ class _HomeScreenState extends State<HomeScreen>
.fetchStoryBy(storyId)
.then((Story? story) {
if (story == null) {
showSnackBar(content: 'Something went wrong...');
showErrorSnackBar();
return;
}
final ItemScreenArgs args = ItemScreenArgs(item: story);
@ -326,7 +326,7 @@ class _HomeScreenState extends State<HomeScreen>
.fetchStoryBy(storyId)
.then((Story? story) {
if (story == null) {
showSnackBar(content: 'Something went wrong...');
showErrorSnackBar();
return;
}
final ItemScreenArgs args = ItemScreenArgs(item: story);

View File

@ -239,12 +239,7 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
context.read<EditCubit>().onReplySubmittedSuccessfully();
context.read<PostCubit>().reset();
} else if (postState.status == PostStatus.failure) {
showSnackBar(
content: 'Something went wrong...'
'${Constants.sadFace}',
label: 'Okay',
action: ScaffoldMessenger.of(context).hideCurrentSnackBar,
);
showErrorSnackBar();
context.read<PostCubit>().reset();
}
},

View File

@ -87,13 +87,13 @@ class LoginDialog extends StatelessWidget {
height: Dimens.pt16,
),
if (state.status == AuthStatus.failure)
const Padding(
padding: EdgeInsets.only(
Padding(
padding: const EdgeInsets.only(
left: Dimens.pt18,
),
child: Text(
'Something went wrong...',
style: TextStyle(
Constants.errorMessage,
style: const TextStyle(
color: Palette.grey,
fontSize: TextDimens.pt12,
),

View File

@ -15,18 +15,12 @@ class MorePopupMenu extends StatelessWidget {
super.key,
required this.item,
required this.isBlocked,
required this.showSnackBar,
required this.onStoryLinkTapped,
required this.onLoginTapped,
});
final Item item;
final bool isBlocked;
final void Function({
required String content,
VoidCallback? action,
String? label,
}) showSnackBar;
final ValueChanged<String> onStoryLinkTapped;
final VoidCallback onLoginTapped;
@ -43,24 +37,26 @@ class MorePopupMenu extends StatelessWidget {
},
listener: (BuildContext context, VoteState voteState) {
if (voteState.status == VoteStatus.submitted) {
showSnackBar(content: 'Vote submitted successfully.');
context.showSnackBar(content: 'Vote submitted successfully.');
} else if (voteState.status == VoteStatus.canceled) {
showSnackBar(content: 'Vote canceled.');
context.showSnackBar(content: 'Vote canceled.');
} else if (voteState.status == VoteStatus.failure) {
showSnackBar(content: 'Something went wrong...');
context.showErrorSnackBar();
} else if (voteState.status ==
VoteStatus.failureKarmaBelowThreshold) {
showSnackBar(
context.showSnackBar(
content: "You can't downvote because you are karmaly broke.",
);
} else if (voteState.status == VoteStatus.failureNotLoggedIn) {
showSnackBar(
context.showSnackBar(
content: 'Not logged in, no voting! (;O´)o',
action: onLoginTapped,
label: 'Log in',
);
} else if (voteState.status == VoteStatus.failureBeHumble) {
showSnackBar(content: 'No voting on your own post! (;O´)o');
context.showSnackBar(
content: 'No voting on your own post! (;O´)o',
);
}
Navigator.pop(

View File

@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_fadein/flutter_fadein.dart';
import 'package:hacki/blocs/auth/auth_bloc.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/context_extension.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/styles/styles.dart';
@ -61,36 +62,29 @@ class PollView extends StatelessWidget {
listener: (BuildContext context, VoteState voteState) {
ScaffoldMessenger.of(context).clearSnackBars();
if (voteState.status == VoteStatus.submitted) {
showSnackBar(
context,
context.showSnackBar(
content: 'Vote submitted successfully.',
);
} else if (voteState.status == VoteStatus.canceled) {
showSnackBar(context, content: 'Vote canceled.');
context.showSnackBar(content: 'Vote canceled.');
} else if (voteState.status == VoteStatus.failure) {
showSnackBar(
context,
content: 'Something went wrong...',
);
context.showErrorSnackBar();
} else if (voteState.status ==
VoteStatus.failureKarmaBelowThreshold) {
showSnackBar(
context,
context.showSnackBar(
content: "You can't downvote because"
' you are karmaly broke.',
);
} else if (voteState.status ==
VoteStatus.failureNotLoggedIn) {
showSnackBar(
context,
context.showSnackBar(
content: 'Not logged in, no voting! (;O´)o',
action: onLoginTapped,
label: 'Log in',
);
} else if (voteState.status ==
VoteStatus.failureBeHumble) {
showSnackBar(
context,
context.showSnackBar(
content: 'No voting on your own post! (;O´)o',
);
}
@ -153,26 +147,4 @@ class PollView extends StatelessWidget {
},
);
}
void showSnackBar(
BuildContext context, {
required String content,
VoidCallback? action,
String? label,
}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Palette.deepOrange,
content: Text(content),
action: action != null && label != null
? SnackBarAction(
label: label,
onPressed: action,
textColor: Theme.of(context).textTheme.bodyLarge?.color,
)
: null,
behavior: SnackBarBehavior.floating,
),
);
}
}

View File

@ -453,13 +453,13 @@ class _ProfileScreenState extends State<ProfileScreen>
height: Dimens.pt16,
),
if (state.status == AuthStatus.failure)
const Padding(
padding: EdgeInsets.only(
Padding(
padding: const EdgeInsets.only(
left: Dimens.pt18,
),
child: Text(
'Something went wrong...',
style: TextStyle(
Constants.errorMessage,
style: const TextStyle(
color: Palette.grey,
fontSize: TextDimens.pt12,
),

View File

@ -50,9 +50,7 @@ class _SubmitScreenState extends State<SubmitScreen> {
content: 'Post submitted successfully.',
);
} else if (state.status == SubmitStatus.failure) {
showSnackBar(
content: 'Something went wrong...',
);
showErrorSnackBar();
}
},
builder: (BuildContext context, SubmitState state) {

View File

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:hacki/styles/styles.dart';
class CenteredText extends StatelessWidget {
const CenteredText({
super.key,
required this.text,
this.color = Palette.grey,
});
const CenteredText.deleted({Key? key})
: this(
key: key,
text: 'deleted',
);
const CenteredText.dead({Key? key})
: this(
key: key,
text: 'dead',
);
const CenteredText.blocked({Key? key})
: this(
key: key,
text: 'blocked',
);
final String text;
final Color color;
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.only(
bottom: Dimens.pt12,
),
child: Text(
text,
style: TextStyle(
color: color,
),
),
),
);
}
}

View File

@ -8,6 +8,7 @@ import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/screens/widgets/bloc_builder_3.dart';
import 'package:hacki/screens/widgets/centered_text.dart';
import 'package:hacki/services/services.dart';
import 'package:hacki/styles/styles.dart';
import 'package:hacki/utils/utils.dart';
@ -40,6 +41,8 @@ class CommentTile extends StatelessWidget {
final void Function(String) onStoryLinkTapped;
final FetchMode fetchMode;
static final Map<int, Color> _colors = <int, Color>{};
@override
Widget build(BuildContext context) {
return BlocProvider<CollapseCubit>(
@ -157,136 +160,92 @@ class CommentTile extends StatelessWidget {
],
),
),
if (actionable && state.collapsed)
Center(
child: Padding(
padding: const EdgeInsets.only(
bottom: Dimens.pt12,
),
child: Text(
'collapsed '
'(${state.collapsedCount + 1})',
style: const TextStyle(
AnimatedSize(
duration: const Duration(milliseconds: 200),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (actionable && state.collapsed)
CenteredText(
text:
'''collapsed (${state.collapsedCount + 1})''',
color: Palette.orangeAccent,
),
),
),
)
else if (comment.deleted)
const Center(
child: Padding(
padding: EdgeInsets.only(
bottom: Dimens.pt12,
),
child: Text(
'deleted',
style: TextStyle(
color: Palette.grey,
),
),
),
)
else if (comment.dead)
const Center(
child: Padding(
padding: EdgeInsets.only(
bottom: Dimens.pt12,
),
child: Text(
'dead',
style: TextStyle(
color: Palette.grey,
),
),
),
)
else if (blocklistState.blocklist.contains(comment.by))
const Center(
child: Padding(
padding: EdgeInsets.only(
bottom: Dimens.pt12,
),
child: Text(
'blocked',
style: TextStyle(
color: Palette.grey,
),
),
),
)
else
Padding(
padding: const EdgeInsets.only(
left: Dimens.pt8,
right: Dimens.pt8,
top: Dimens.pt6,
bottom: Dimens.pt12,
),
child: comment is BuildableComment
? SelectableText.rich(
key: ValueKey<int>(comment.id),
buildTextSpan(
(comment as BuildableComment).elements,
style: TextStyle(
fontSize: MediaQuery.of(
context,
).textScaleFactor *
prefState.fontSize.fontSize,
),
linkStyle: TextStyle(
fontSize: MediaQuery.of(
context,
).textScaleFactor *
prefState.fontSize.fontSize,
decoration: TextDecoration.underline,
color: Palette.orange,
),
onOpen: (LinkableElement link) {
if (link.url.isStoryLink) {
onStoryLinkTapped.call(link.url);
} else {
LinkUtil.launch(link.url);
}
},
),
onTap: () => onTextTapped(context),
)
: SelectableLinkify(
key: ValueKey<int>(comment.id),
text: comment.text,
style: TextStyle(
fontSize: MediaQuery.of(context)
.textScaleFactor *
prefState.fontSize.fontSize,
),
linkStyle: TextStyle(
fontSize: MediaQuery.of(context)
.textScaleFactor *
prefState.fontSize.fontSize,
color: Palette.orange,
),
onOpen: (LinkableElement link) {
if (link.url.isStoryLink) {
onStoryLinkTapped.call(link.url);
} else {
LinkUtil.launch(link.url);
}
},
onTap: () => onTextTapped(context),
)
else if (comment.deleted)
const CenteredText.deleted()
else if (comment.dead)
const CenteredText.dead()
else if (blocklistState.blocklist
.contains(comment.by))
const CenteredText.blocked()
else
Padding(
padding: const EdgeInsets.only(
left: Dimens.pt8,
right: Dimens.pt8,
top: Dimens.pt6,
bottom: Dimens.pt12,
),
child: comment is BuildableComment
? SelectableText.rich(
key: ValueKey<int>(comment.id),
buildTextSpan(
(comment as BuildableComment)
.elements,
style: TextStyle(
fontSize: MediaQuery.of(
context,
).textScaleFactor *
prefState.fontSize.fontSize,
),
linkStyle: TextStyle(
fontSize: MediaQuery.of(
context,
).textScaleFactor *
prefState.fontSize.fontSize,
decoration:
TextDecoration.underline,
color: Palette.orange,
),
onOpen: (LinkableElement link) {
if (link.url.isStoryLink) {
onStoryLinkTapped
.call(link.url);
} else {
LinkUtil.launch(link.url);
}
},
),
onTap: () => onTextTapped(context),
)
: SelectableLinkify(
key: ValueKey<int>(comment.id),
text: comment.text,
style: TextStyle(
fontSize: MediaQuery.of(context)
.textScaleFactor *
prefState.fontSize.fontSize,
),
linkStyle: TextStyle(
fontSize: MediaQuery.of(context)
.textScaleFactor *
prefState.fontSize.fontSize,
color: Palette.orange,
),
onOpen: (LinkableElement link) {
if (link.url.isStoryLink) {
onStoryLinkTapped.call(link.url);
} else {
LinkUtil.launch(link.url);
}
},
onTap: () => onTextTapped(context),
),
),
],
),
if (!state.collapsed &&
fetchMode == FetchMode.lazy &&
comment.kids.isNotEmpty &&
!context
.read<CommentsCubit>()
.state
.commentIds
.contains(comment.kids.first) &&
!context
.read<CommentsCubit>()
.state
.onlyShowTargetComment)
),
if (_shouldShowLoadButton(context))
Center(
child: Padding(
padding: const EdgeInsets.symmetric(
@ -376,7 +335,12 @@ class CommentTile extends StatelessWidget {
);
}
static final Map<int, Color> _colors = <int, Color>{};
void onTextTapped(BuildContext context) {
if (context.read<PreferenceCubit>().state.tapAnywhereToCollapseEnabled) {
HapticFeedback.selectionClick();
context.read<CollapseCubit>().collapse();
}
}
Color _getColor(int level) {
final int initialLevel = level;
@ -406,10 +370,13 @@ class CommentTile extends StatelessWidget {
return color;
}
void onTextTapped(BuildContext context) {
if (context.read<PreferenceCubit>().state.tapAnywhereToCollapseEnabled) {
HapticFeedback.selectionClick();
context.read<CollapseCubit>().collapse();
}
bool _shouldShowLoadButton(BuildContext context) {
final CollapseState collapseState = context.read<CollapseCubit>().state;
final CommentsState commentsState = context.read<CommentsCubit>().state;
return fetchMode == FetchMode.lazy &&
comment.kids.isNotEmpty &&
collapseState.collapsed == false &&
commentsState.commentIds.contains(comment.kids.first) == false &&
commentsState.onlyShowTargetComment == false;
}
}

View File

@ -111,7 +111,7 @@ class _CountDownReminderState extends State<CountdownReminder>
.fetchStoryBy(state.storyId!)
.then((Story? story) {
if (story == null) {
showSnackBar(content: 'Something went wrong...');
showErrorSnackBar();
return;
}
final ItemScreenArgs args = ItemScreenArgs(item: story);

View File

@ -5,7 +5,7 @@ import 'package:hacki/config/constants.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/screens/widgets/link_preview/link_view.dart';
import 'package:hacki/screens/widgets/link_preview/web_analyzer.dart';
import 'package:hacki/services/services.dart';
import 'package:hacki/styles/styles.dart';
import 'package:url_launcher/url_launcher.dart';
@ -119,7 +119,7 @@ class _LinkPreviewState extends State<LinkPreview> {
@override
void initState() {
_errorTitle = widget.errorTitle ?? 'Something went wrong!';
_errorTitle = widget.errorTitle ?? Constants.errorMessage;
_errorBody = widget.errorBody ??
'Oops! Unable to parse the url. We have '
'sent feedback to our developers & '

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hacki/blocs/blocs.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/screens/widgets/link_preview/web_analyzer.dart';
import 'package:hacki/services/services.dart';
import 'package:hacki/styles/styles.dart';
class OfflineBanner extends StatelessWidget {

View File

@ -1,4 +1,5 @@
export 'bloc_builder_3.dart';
export 'centered_text.dart';
export 'circle_tab_indicator.dart';
export 'comment_tile.dart';
export 'countdown_reminder.dart';

View File

@ -3,3 +3,4 @@ export 'custom_bloc_observer.dart';
export 'fetcher.dart';
export 'firebase_client.dart';
export 'local_notification.dart';
export 'web_analyzer.dart';

View File

@ -14,7 +14,7 @@ abstract class LogUtil {
colors: false,
);
static LogOutput getLogOutput(File outputFile) => MultiOutput(
static LogOutput logOutput(File outputFile) => MultiOutput(
<LogOutput>[
ConsoleOutput(),
CustomFileOutput(
@ -43,7 +43,7 @@ abstract class LogUtil {
final Uint8List fileContent = await currentSessionLog.readAsBytes();
await previousSessionLog.writeAsString(
'Current session logs:',
'Current session logs:\n',
mode: FileMode.append,
);
return previousSessionLog.writeAsBytes(

View File

@ -1359,4 +1359,4 @@ packages:
version: "3.1.1"
sdks:
dart: ">=2.18.0 <3.0.0"
flutter: ">=3.7.1"
flutter: ">=3.7.3"

View File

@ -1,11 +1,11 @@
name: hacki
description: A Hacker News reader.
version: 1.0.7+85
version: 1.0.10+88
publish_to: none
environment:
sdk: ">=2.17.0 <3.0.0"
flutter: "3.7.1"
flutter: "3.7.3"
dependencies:
adaptive_theme: ^3.0.0