mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
764ff09345 | |||
ab449adce2 | |||
2ec41b26f2 | |||
19f2107d95 |
@ -1,14 +1,13 @@
|
||||
|
||||
# <img width="64" src="https://user-images.githubusercontent.com/7277662/167775086-0b234f28-dee4-44f6-aae4-14a28ed4bbb6.png"> Hacki for Hacker News
|
||||
|
||||
A simple noiseless [Hacker News](https://news.ycombinator.com/) client made with Flutter that is just enough.
|
||||
A [Hacker News](https://news.ycombinator.com/) client made with Flutter that is just enough.
|
||||
|
||||
[](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone)
|
||||
[](https://f-droid.org/en/packages/com.jiaqifeng.hacki/)
|
||||
[](https://github.com/Livinglist/Hacki/releases/latest)
|
||||
[](https://badges.pufler.dev)
|
||||
[](https://img.shields.io/github/stars/livinglist/Hacki?style=social)
|
||||
[](https://pub.dev/packages/effective_dart)
|
||||
[](https://img.shields.io/github/stars/livinglist/Hacki?style=social)
|
||||
|
||||
[<img src="assets/images/app_store_badge.png" height="50">](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone) [<img src="assets/images/google_play_badge.png" height="50">](https://play.google.com/store/apps/details?id=com.jiaqifeng.hacki&hl=en_US&gl=US) [<img src="assets/images/f_droid_badge.png" height="50">](https://f-droid.org/en/packages/com.jiaqifeng.hacki/)
|
||||
|
||||
|
3
fastlane/metadata/android/en-US/changelogs/70.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/70.txt
Normal file
@ -0,0 +1,3 @@
|
||||
- Lazy loading.
|
||||
- Offline mode now includes web pages.
|
||||
- You can now sort comments in story screen.
|
3
fastlane/metadata/android/en-US/changelogs/71.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/71.txt
Normal file
@ -0,0 +1,3 @@
|
||||
- Lazy loading.
|
||||
- Offline mode now includes web pages.
|
||||
- You can now sort comments in story screen.
|
3
fastlane/metadata/android/en-US/changelogs/72.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/72.txt
Normal file
@ -0,0 +1,3 @@
|
||||
- Lazy loading.
|
||||
- Offline mode now includes web pages.
|
||||
- You can now sort comments in story screen.
|
@ -577,7 +577,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.2.27;
|
||||
MARKETING_VERSION = 0.2.29;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -714,7 +714,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.2.27;
|
||||
MARKETING_VERSION = 0.2.29;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -745,7 +745,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.2.27;
|
||||
MARKETING_VERSION = 0.2.29;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -11,6 +11,7 @@ import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/repositories/repositories.dart';
|
||||
import 'package:hacki/screens/screens.dart';
|
||||
import 'package:hacki/services/services.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
part 'comments_state.dart';
|
||||
|
||||
@ -21,6 +22,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
OfflineRepository? offlineRepository,
|
||||
StoriesRepository? storiesRepository,
|
||||
SembastRepository? sembastRepository,
|
||||
Logger? logger,
|
||||
required bool offlineReading,
|
||||
required Item item,
|
||||
required FetchMode defaultFetchMode,
|
||||
@ -33,6 +35,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
storiesRepository ?? locator.get<StoriesRepository>(),
|
||||
_sembastRepository =
|
||||
sembastRepository ?? locator.get<SembastRepository>(),
|
||||
_logger = logger ?? locator.get<Logger>(),
|
||||
super(
|
||||
CommentsState.init(
|
||||
offlineReading: offlineReading,
|
||||
@ -47,8 +50,17 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
final OfflineRepository _offlineRepository;
|
||||
final StoriesRepository _storiesRepository;
|
||||
final SembastRepository _sembastRepository;
|
||||
final Logger _logger;
|
||||
|
||||
/// The [StreamSubscription] for stream (both lazy or eager)
|
||||
/// fetching comments posted directly to the story.
|
||||
StreamSubscription<Comment>? _streamSubscription;
|
||||
|
||||
/// The map of [StreamSubscription] for streams
|
||||
/// fetching comments lazily. [int] is the id of parent comment.
|
||||
final Map<int, StreamSubscription<Comment>> _streamSubscriptions =
|
||||
<int, StreamSubscription<Comment>>{};
|
||||
|
||||
static const int _pageSize = 20;
|
||||
|
||||
@override
|
||||
@ -73,7 +85,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
);
|
||||
|
||||
_streamSubscription = _storiesRepository
|
||||
.fetchAllCommentsStream(
|
||||
.fetchAllCommentsRecursivelyStream(
|
||||
ids: targetParents!.last.kids,
|
||||
level: targetParents.last.level + 1,
|
||||
)
|
||||
@ -105,22 +117,25 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
.listen(_onCommentFetched)
|
||||
..onDone(_onDone);
|
||||
} else {
|
||||
if (state.fetchMode == FetchMode.lazy) {
|
||||
_streamSubscription = _storiesRepository
|
||||
.fetchCommentsStream(
|
||||
ids: kids,
|
||||
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
||||
)
|
||||
.listen(_onCommentFetched)
|
||||
..onDone(_onDone);
|
||||
} else {
|
||||
_streamSubscription = _storiesRepository
|
||||
.fetchAllCommentsStream(
|
||||
ids: kids,
|
||||
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
||||
)
|
||||
.listen(_onCommentFetched)
|
||||
..onDone(_onDone);
|
||||
switch (state.fetchMode) {
|
||||
case FetchMode.lazy:
|
||||
_streamSubscription = _storiesRepository
|
||||
.fetchCommentsStream(
|
||||
ids: kids,
|
||||
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
||||
)
|
||||
.listen(_onCommentFetched)
|
||||
..onDone(_onDone);
|
||||
break;
|
||||
case FetchMode.eager:
|
||||
_streamSubscription = _storiesRepository
|
||||
.fetchAllCommentsRecursivelyStream(
|
||||
ids: kids,
|
||||
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
||||
)
|
||||
.listen(_onCommentFetched)
|
||||
..onDone(_onDone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,18 +150,27 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
return;
|
||||
}
|
||||
|
||||
_collapseCache.resetCollapsedComments();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CommentsStatus.loading,
|
||||
),
|
||||
);
|
||||
|
||||
_collapseCache.resetCollapsedComments();
|
||||
|
||||
await _streamSubscription?.cancel();
|
||||
for (final int id in _streamSubscriptions.keys) {
|
||||
await _streamSubscriptions[id]?.cancel();
|
||||
}
|
||||
_streamSubscriptions.clear();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
comments: <Comment>[],
|
||||
currentPage: 0,
|
||||
),
|
||||
);
|
||||
|
||||
await _streamSubscription?.cancel();
|
||||
|
||||
final Item item = state.item;
|
||||
final Item updatedItem =
|
||||
await _storiesRepository.fetchItemBy(id: item.id) ?? item;
|
||||
@ -161,7 +185,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
..onDone(_onDone);
|
||||
} else {
|
||||
_streamSubscription = _storiesRepository
|
||||
.fetchAllCommentsStream(
|
||||
.fetchAllCommentsRecursivelyStream(
|
||||
ids: kids,
|
||||
)
|
||||
.listen(_onCommentFetched)
|
||||
@ -189,20 +213,20 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
|
||||
/// [comment] is only used for lazy fetching.
|
||||
void loadMore({Comment? comment}) {
|
||||
if (state.fetchMode == FetchMode.eager) {
|
||||
if (_streamSubscription != null) {
|
||||
emit(state.copyWith(status: CommentsStatus.loading));
|
||||
_streamSubscription?.resume();
|
||||
}
|
||||
} else {
|
||||
if (comment == null) return;
|
||||
switch (state.fetchMode) {
|
||||
case FetchMode.lazy:
|
||||
if (comment == null) return;
|
||||
if (_streamSubscriptions.containsKey(comment.id)) return;
|
||||
|
||||
final int level = comment.level + 1;
|
||||
int offset = 0;
|
||||
final int level = comment.level + 1;
|
||||
int offset = 0;
|
||||
|
||||
_streamSubscription = _streamSubscription =
|
||||
_storiesRepository.fetchCommentsStream(ids: comment.kids).listen(
|
||||
(Comment cmt) {
|
||||
/// Ignoring because the subscription will be cancelled in close()
|
||||
// ignore: cancel_subscriptions
|
||||
final StreamSubscription<Comment> streamSubscription =
|
||||
_storiesRepository
|
||||
.fetchCommentsStream(ids: comment.kids)
|
||||
.listen((Comment cmt) {
|
||||
_collapseCache.addKid(cmt.id, to: cmt.parent);
|
||||
_commentCache.cacheComment(cmt);
|
||||
_sembastRepository.cacheComment(cmt);
|
||||
@ -223,8 +247,25 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
),
|
||||
);
|
||||
offset++;
|
||||
},
|
||||
);
|
||||
})
|
||||
..onDone(() {
|
||||
_streamSubscriptions[comment.id]?.cancel();
|
||||
_streamSubscriptions.remove(comment.id);
|
||||
})
|
||||
..onError((dynamic error) {
|
||||
_logger.e(error);
|
||||
_streamSubscriptions[comment.id]?.cancel();
|
||||
_streamSubscriptions.remove(comment.id);
|
||||
});
|
||||
|
||||
_streamSubscriptions[comment.id] = streamSubscription;
|
||||
break;
|
||||
case FetchMode.eager:
|
||||
if (_streamSubscription != null) {
|
||||
emit(state.copyWith(status: CommentsStatus.loading));
|
||||
_streamSubscription?.resume();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,6 +296,10 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
if (state.order == order) return;
|
||||
HapticFeedback.selectionClick();
|
||||
_streamSubscription?.cancel();
|
||||
for (final StreamSubscription<Comment> s in _streamSubscriptions.values) {
|
||||
s.cancel();
|
||||
}
|
||||
_streamSubscriptions.clear();
|
||||
emit(state.copyWith(order: order));
|
||||
init(useCommentCache: true);
|
||||
}
|
||||
@ -265,6 +310,10 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
_collapseCache.resetCollapsedComments();
|
||||
HapticFeedback.selectionClick();
|
||||
_streamSubscription?.cancel();
|
||||
for (final StreamSubscription<Comment> s in _streamSubscriptions.values) {
|
||||
s.cancel();
|
||||
}
|
||||
_streamSubscriptions.clear();
|
||||
emit(state.copyWith(fetchMode: fetchMode));
|
||||
init(useCommentCache: true);
|
||||
}
|
||||
@ -360,6 +409,9 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _streamSubscription?.cancel();
|
||||
for (final StreamSubscription<Comment> s in _streamSubscriptions.values) {
|
||||
await s.cancel();
|
||||
}
|
||||
await super.close();
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ class CommentsState extends Equatable {
|
||||
);
|
||||
}
|
||||
|
||||
Set<int> get commentIds => comments.map((Comment e) => e.id).toSet();
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
item,
|
||||
|
@ -33,7 +33,8 @@ class NotificationCubit extends Cubit<NotificationState> {
|
||||
_preferenceRepository.shouldShowNotification
|
||||
.then((bool showNotification) {
|
||||
if (showNotification) {
|
||||
init();
|
||||
// Delaying the initialization to prevent janks in home screen.
|
||||
Future<void>.delayed(const Duration(seconds: 2), init);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hacki/config/locator.dart';
|
||||
import 'package:hacki/cubits/comments/comments_cubit.dart';
|
||||
import 'package:hacki/repositories/repositories.dart';
|
||||
@ -82,12 +83,14 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
||||
|
||||
void selectFetchMode(FetchMode? fetchMode) {
|
||||
if (fetchMode == null || state.fetchMode == fetchMode) return;
|
||||
HapticFeedback.lightImpact();
|
||||
emit(state.copyWith(fetchMode: fetchMode));
|
||||
_preferenceRepository.selectFetchMode(fetchMode);
|
||||
}
|
||||
|
||||
void selectCommentsOrder(CommentsOrder? order) {
|
||||
if (order == null || state.order == order) return;
|
||||
HapticFeedback.lightImpact();
|
||||
emit(state.copyWith(order: order));
|
||||
_preferenceRepository.selectCommentsOrder(order);
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
extension TryReadContext on BuildContext {
|
||||
extension ContextExtension on BuildContext {
|
||||
T? tryRead<T>() {
|
||||
try {
|
||||
return read<T>();
|
||||
@ -16,4 +18,41 @@ extension TryReadContext on BuildContext {
|
||||
box == null ? null : box.localToGlobal(Offset.zero) & box.size;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static double _screenWidth = 0;
|
||||
static double _storyTileHeight = 0;
|
||||
static int _storyTileMaxLines = 4;
|
||||
static const double _screenWidthLowerBound = 428,
|
||||
_screenWidthUpperBound = 850,
|
||||
_picHeightLowerBound = 110,
|
||||
_picHeightUpperBound = 128,
|
||||
_smallPicHeight = 100,
|
||||
_picHeightFactor = 0.3;
|
||||
|
||||
double get storyTileHeight {
|
||||
final double screenWidth =
|
||||
min(MediaQuery.of(this).size.height, MediaQuery.of(this).size.width);
|
||||
|
||||
if (screenWidth == _screenWidth) {
|
||||
return _storyTileHeight;
|
||||
} else {
|
||||
_screenWidth = screenWidth;
|
||||
}
|
||||
|
||||
final bool showSmallerPreviewPic = screenWidth > _screenWidthLowerBound &&
|
||||
screenWidth < _screenWidthUpperBound;
|
||||
final double height = showSmallerPreviewPic
|
||||
? _smallPicHeight
|
||||
: (screenWidth * _picHeightFactor)
|
||||
.clamp(_picHeightLowerBound, _picHeightUpperBound);
|
||||
final int maxLines = height == _smallPicHeight ? 3 : 4;
|
||||
_storyTileMaxLines = maxLines;
|
||||
|
||||
_storyTileHeight = height;
|
||||
return height;
|
||||
}
|
||||
|
||||
int get storyTileMaxLines {
|
||||
return _storyTileMaxLines;
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,8 @@ extension DateTimeExtension on DateTime {
|
||||
return '$gap year${gap == 1 ? '' : 's'} ago';
|
||||
} else if (diff.inDays > 30) {
|
||||
int gap = now.month - month;
|
||||
if (gap == 0) {
|
||||
gap = 1;
|
||||
} else if (gap < 0) {
|
||||
gap = now.month + (12 - month);
|
||||
if (gap <= 0) {
|
||||
gap = now.month + 12 - month;
|
||||
}
|
||||
return '$gap month${gap == 1 ? '' : 's'} ago';
|
||||
} else if (diff.inDays >= 1) {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class User {
|
||||
@ -39,8 +37,6 @@ class User {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final String prettyString =
|
||||
const JsonEncoder.withIndent(' ').convert(this);
|
||||
return 'User $prettyString';
|
||||
return 'User $about, $created, $delay, $id, $karma';
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ class StoriesRepository {
|
||||
return;
|
||||
}
|
||||
|
||||
Stream<Comment> fetchAllCommentsStream({
|
||||
Stream<Comment> fetchAllCommentsRecursivelyStream({
|
||||
required List<int> ids,
|
||||
int level = 0,
|
||||
Comment? Function(int)? getFromCache,
|
||||
@ -94,7 +94,7 @@ class StoriesRepository {
|
||||
if (comment != null) {
|
||||
yield comment;
|
||||
|
||||
yield* fetchAllCommentsStream(
|
||||
yield* fetchAllCommentsRecursivelyStream(
|
||||
ids: comment.kids,
|
||||
level: level + 1,
|
||||
getFromCache: getFromCache,
|
||||
|
@ -502,7 +502,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
Text(
|
||||
'''${state.item.score} karma, ${state.item.descendants} comment${state.item.descendants > 1 ? 's' : ''}''',
|
||||
style: const TextStyle(
|
||||
fontSize: TextDimens.pt12,
|
||||
fontSize: TextDimens.pt13,
|
||||
),
|
||||
),
|
||||
] else ...<Widget>[
|
||||
@ -523,7 +523,12 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
strokeWidth: Dimens.pt2,
|
||||
),
|
||||
)
|
||||
: const Text('View parent thread'),
|
||||
: const Text(
|
||||
'View parent thread',
|
||||
style: TextStyle(
|
||||
fontSize: TextDimens.pt13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
@ -537,7 +542,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
child: Text(
|
||||
'Lazy',
|
||||
style: TextStyle(
|
||||
fontSize: TextDimens.pt12,
|
||||
fontSize: TextDimens.pt13,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -546,7 +551,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
child: Text(
|
||||
'Eager',
|
||||
style: TextStyle(
|
||||
fontSize: TextDimens.pt12,
|
||||
fontSize: TextDimens.pt13,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -568,7 +573,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
child: Text(
|
||||
'Natural',
|
||||
style: TextStyle(
|
||||
fontSize: TextDimens.pt12,
|
||||
fontSize: TextDimens.pt13,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -577,7 +582,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
child: Text(
|
||||
'Newest first',
|
||||
style: TextStyle(
|
||||
fontSize: TextDimens.pt12,
|
||||
fontSize: TextDimens.pt13,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -586,7 +591,7 @@ class _ItemScreenState extends State<ItemScreen> {
|
||||
child: Text(
|
||||
'Oldest first',
|
||||
style: TextStyle(
|
||||
fontSize: TextDimens.pt12,
|
||||
fontSize: TextDimens.pt13,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -526,7 +526,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'Hacki',
|
||||
applicationVersion: 'v0.2.27',
|
||||
applicationVersion: 'v0.2.29',
|
||||
applicationIcon: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(
|
||||
|
@ -288,9 +288,7 @@ class CommentTile extends StatelessWidget {
|
||||
!context
|
||||
.read<CommentsCubit>()
|
||||
.state
|
||||
.comments
|
||||
.map((Comment e) => e.id)
|
||||
.toSet()
|
||||
.commentIds
|
||||
.contains(comment.kids.first))
|
||||
Center(
|
||||
child: TextButton(
|
||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
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';
|
||||
@ -199,23 +200,9 @@ class _LinkPreviewState extends State<LinkPreview> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double screenWidthLowerBound = 428,
|
||||
screenWidthUpperBound = 850,
|
||||
picHeightLowerBound = 118,
|
||||
picHeightUpperBound = 140,
|
||||
smallPicHeight = 100,
|
||||
picHeightFactor = 0.14;
|
||||
final double screenWidth = MediaQuery.of(context).size.width;
|
||||
final bool showSmallerPreviewPic = screenWidth > screenWidthLowerBound &&
|
||||
screenWidth < screenWidthUpperBound;
|
||||
final double height = showSmallerPreviewPic
|
||||
? smallPicHeight
|
||||
: (MediaQuery.of(context).size.height * picHeightFactor)
|
||||
.clamp(picHeightLowerBound, picHeightUpperBound);
|
||||
|
||||
final Widget loadingWidget = widget.placeholderWidget ??
|
||||
Container(
|
||||
height: height,
|
||||
height: context.storyTileHeight,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
@ -232,13 +219,13 @@ class _LinkPreviewState extends State<LinkPreview> {
|
||||
final WebInfo? info = _info as WebInfo?;
|
||||
loadedWidget = _info == null
|
||||
? _buildLinkContainer(
|
||||
height,
|
||||
context.storyTileHeight,
|
||||
title: _errorTitle,
|
||||
desc: _errorBody,
|
||||
imageUri: null,
|
||||
)
|
||||
: _buildLinkContainer(
|
||||
height,
|
||||
context.storyTileHeight,
|
||||
title: _errorTitle,
|
||||
desc: WebAnalyzer.isNotEmpty(info!.description)
|
||||
? info.description
|
||||
|
@ -147,7 +147,7 @@ class LinkView extends StatelessWidget {
|
||||
|
||||
Widget _buildTitleContainer(TextStyle _titleTS, int _maxLines) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(4, 2, 3, 1),
|
||||
padding: const EdgeInsets.fromLTRB(4, 2, 3, 0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
@ -168,7 +168,7 @@ class LinkView extends StatelessWidget {
|
||||
return Expanded(
|
||||
flex: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(5, 3, 5, 0),
|
||||
padding: const EdgeInsets.fromLTRB(5, 2, 5, 0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
if (showMetadata)
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_fadein/flutter_fadein.dart';
|
||||
import 'package:hacki/blocs/blocs.dart';
|
||||
import 'package:hacki/config/constants.dart';
|
||||
import 'package:hacki/extensions/extensions.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/screens/widgets/widgets.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
@ -29,20 +30,7 @@ class StoryTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (showWebPreview) {
|
||||
const double screenWidthLowerBound = 428,
|
||||
screenWidthUpperBound = 850,
|
||||
picHeightLowerBound = 118,
|
||||
picHeightUpperBound = 140,
|
||||
smallPicHeight = 100,
|
||||
picHeightFactor = 0.14;
|
||||
final double screenWidth = MediaQuery.of(context).size.width;
|
||||
final bool showSmallerPreviewPic = screenWidth > screenWidthLowerBound &&
|
||||
screenWidth < screenWidthUpperBound;
|
||||
final double height = showSmallerPreviewPic
|
||||
? smallPicHeight
|
||||
: (MediaQuery.of(context).size.height * picHeightFactor)
|
||||
.clamp(picHeightLowerBound, picHeightUpperBound);
|
||||
|
||||
final double height = context.storyTileHeight;
|
||||
return TapDownWrapper(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
@ -143,7 +131,7 @@ class StoryTile extends StatelessWidget {
|
||||
backgroundColor: Palette.transparent,
|
||||
borderRadius: Dimens.zero,
|
||||
removeElevation: true,
|
||||
bodyMaxLines: height == smallPicHeight ? 3 : 4,
|
||||
bodyMaxLines: context.storyTileMaxLines,
|
||||
errorTitle: story.title,
|
||||
titleStyle: TextStyle(
|
||||
color: hasRead
|
||||
|
@ -27,6 +27,7 @@ abstract class TextDimens {
|
||||
static const double pt8 = 8;
|
||||
static const double pt10 = 10;
|
||||
static const double pt12 = 12;
|
||||
static const double pt13 = 13;
|
||||
static const double pt14 = 14;
|
||||
static const double pt15 = 15;
|
||||
static const double pt16 = 16;
|
||||
|
@ -1,6 +1,6 @@
|
||||
name: hacki
|
||||
description: A Hacker News reader.
|
||||
version: 0.2.27+69
|
||||
version: 0.2.29+72
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
Reference in New Issue
Block a user