Compare commits

...

1 Commits

Author SHA1 Message Date
fe162208ca fix expand animation. (#142) 2023-02-10 14:08:31 -08:00
8 changed files with 149 additions and 171 deletions

View File

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

View File

@ -14,15 +14,13 @@ class PostRepository extends PostableRepository {
final PreferenceRepository _preferenceRepository; final PreferenceRepository _preferenceRepository;
static const String _authority = 'news.ycombinator.com';
Future<bool> comment({ Future<bool> comment({
required int parentId, required int parentId,
required String text, required String text,
}) async { }) async {
final String? username = await _preferenceRepository.username; final String? username = await _preferenceRepository.username;
final String? password = await _preferenceRepository.password; 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) { if (username == null || password == null) {
return false; return false;
@ -54,7 +52,7 @@ class PostRepository extends PostableRepository {
return false; return false;
} }
final Response<List<int>> formResponse = await _getFormResponse( final Response<List<int>> formResponse = await getFormResponse(
username: username, username: username,
password: password, password: password,
path: 'submitlink', path: 'submitlink',
@ -69,7 +67,7 @@ class PostRepository extends PostableRepository {
final String? cookie = final String? cookie =
formResponse.headers.value(HttpHeaders.setCookieHeader); formResponse.headers.value(HttpHeaders.setCookieHeader);
final Uri uri = Uri.https(_authority, 'r'); final Uri uri = Uri.https(authority, 'r');
final PostDataMixin data = SubmitPostData( final PostDataMixin data = SubmitPostData(
fnid: formValues['fnid']!, fnid: formValues['fnid']!,
fnop: formValues['fnop']!, fnop: formValues['fnop']!,
@ -97,7 +95,7 @@ class PostRepository extends PostableRepository {
return false; return false;
} }
final Response<List<int>> formResponse = await _getFormResponse( final Response<List<int>> formResponse = await getFormResponse(
username: username, username: username,
password: password, password: password,
id: id, id: id,
@ -113,7 +111,7 @@ class PostRepository extends PostableRepository {
final String? cookie = final String? cookie =
formResponse.headers.value(HttpHeaders.setCookieHeader); formResponse.headers.value(HttpHeaders.setCookieHeader);
final Uri uri = Uri.https(_authority, 'xedit'); final Uri uri = Uri.https(authority, 'xedit');
final PostDataMixin data = EditPostData( final PostDataMixin data = EditPostData(
hmac: formValues['hmac']!, hmac: formValues['hmac']!,
id: id, id: id,
@ -126,28 +124,4 @@ class PostRepository extends PostableRepository {
cookie: cookie, 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 { class PostableRepository {
PostableRepository({ PostableRepository({
Dio? dio, Dio? dio,
this.authority = 'news.ycombinator.com',
}) : _dio = dio ?? Dio(); }) : _dio = dio ?? Dio();
final Dio _dio; final Dio _dio;
@protected
final String authority;
@protected @protected
Future<bool> performDefaultPost( Future<bool> performDefaultPost(
Uri uri, Uri uri,
@ -60,4 +64,29 @@ class PostableRepository {
throw ServiceException(e.message); 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

@ -62,8 +62,7 @@ class PollView extends StatelessWidget {
listener: (BuildContext context, VoteState voteState) { listener: (BuildContext context, VoteState voteState) {
ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).clearSnackBars();
if (voteState.status == VoteStatus.submitted) { if (voteState.status == VoteStatus.submitted) {
showSnackBar( context.showSnackBar(
context,
content: 'Vote submitted successfully.', content: 'Vote submitted successfully.',
); );
} else if (voteState.status == VoteStatus.canceled) { } else if (voteState.status == VoteStatus.canceled) {
@ -85,8 +84,7 @@ class PollView extends StatelessWidget {
); );
} else if (voteState.status == } else if (voteState.status ==
VoteStatus.failureBeHumble) { VoteStatus.failureBeHumble) {
showSnackBar( context.showSnackBar(
context,
content: 'No voting on your own post! (;O´)o', content: 'No voting on your own post! (;O´)o',
); );
} }
@ -149,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

@ -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/extensions/extensions.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hacki/screens/widgets/bloc_builder_3.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/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';
@ -40,6 +41,8 @@ class CommentTile extends StatelessWidget {
final void Function(String) onStoryLinkTapped; final void Function(String) onStoryLinkTapped;
final FetchMode fetchMode; final FetchMode fetchMode;
static final Map<int, Color> _colors = <int, Color>{};
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<CollapseCubit>( return BlocProvider<CollapseCubit>(
@ -163,63 +166,18 @@ class CommentTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
if (actionable && state.collapsed) if (actionable && state.collapsed)
Center( CenteredText(
child: Padding( text:
padding: const EdgeInsets.only( '''collapsed (${state.collapsedCount + 1})''',
bottom: Dimens.pt12,
),
child: Text(
'collapsed '
'(${state.collapsedCount + 1})',
style: const TextStyle(
color: Palette.orangeAccent, color: Palette.orangeAccent,
),
),
),
) )
else if (comment.deleted) else if (comment.deleted)
const Center( const CenteredText.deleted()
child: Padding(
padding: EdgeInsets.only(
bottom: Dimens.pt12,
),
child: Text(
'deleted',
style: TextStyle(
color: Palette.grey,
),
),
),
)
else if (comment.dead) else if (comment.dead)
const Center( const CenteredText.dead()
child: Padding(
padding: EdgeInsets.only(
bottom: Dimens.pt12,
),
child: Text(
'dead',
style: TextStyle(
color: Palette.grey,
),
),
),
)
else if (blocklistState.blocklist else if (blocklistState.blocklist
.contains(comment.by)) .contains(comment.by))
const Center( const CenteredText.blocked()
child: Padding(
padding: EdgeInsets.only(
bottom: Dimens.pt12,
),
child: Text(
'blocked',
style: TextStyle(
color: Palette.grey,
),
),
),
)
else else
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@ -284,34 +242,23 @@ class CommentTile extends StatelessWidget {
onTap: () => onTextTapped(context), onTap: () => onTextTapped(context),
), ),
), ),
if (!state.collapsed && ],
fetchMode == FetchMode.lazy && ),
comment.kids.isNotEmpty && ),
!context if (_shouldShowLoadButton(context))
.read<CommentsCubit>()
.state
.commentIds
.contains(comment.kids.first) &&
!context
.read<CommentsCubit>()
.state
.onlyShowTargetComment)
Center( Center(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: Dimens.pt12, horizontal: Dimens.pt12,
), ),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.center,
MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
context context.read<CommentsCubit>().loadMore(
.read<CommentsCubit>()
.loadMore(
comment: comment, comment: comment,
); );
}, },
@ -332,9 +279,6 @@ class CommentTile extends StatelessWidget {
), ),
], ],
), ),
)
],
),
), ),
), ),
], ],
@ -391,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) { Color _getColor(int level) {
final int initialLevel = level; final int initialLevel = level;
@ -421,10 +370,13 @@ class CommentTile extends StatelessWidget {
return color; return color;
} }
void onTextTapped(BuildContext context) { bool _shouldShowLoadButton(BuildContext context) {
if (context.read<PreferenceCubit>().state.tapAnywhereToCollapseEnabled) { final CollapseState collapseState = context.read<CollapseCubit>().state;
HapticFeedback.selectionClick(); final CommentsState commentsState = context.read<CommentsCubit>().state;
context.read<CollapseCubit>().collapse(); return fetchMode == FetchMode.lazy &&
} comment.kids.isNotEmpty &&
collapseState.collapsed == false &&
commentsState.commentIds.contains(comment.kids.first) == false &&
commentsState.onlyShowTargetComment == false;
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
name: hacki name: hacki
description: A Hacker News reader. description: A Hacker News reader.
version: 1.0.9+87 version: 1.0.10+88
publish_to: none publish_to: none
environment: environment: