mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
d4778d9530 | |||
c702e08481 | |||
2af10391bc |
@ -50,7 +50,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.jiaqifeng.hacki"
|
applicationId "com.jiaqifeng.hacki"
|
||||||
minSdkVersion 26
|
minSdkVersion 30
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
@ -160,6 +160,13 @@ class FavCubit extends Cubit<FavState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void removeAll() {
|
||||||
|
_preferenceRepository
|
||||||
|
..clearAllFavs(username: '')
|
||||||
|
..clearAllFavs(username: _authBloc.state.username);
|
||||||
|
emit(FavState.init());
|
||||||
|
}
|
||||||
|
|
||||||
void _onItemLoaded(Item item) {
|
void _onItemLoaded(Item item) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
|
@ -5,4 +5,15 @@ extension ObjectExtension on Object {
|
|||||||
void log({String identifier = ''}) {
|
void log({String identifier = ''}) {
|
||||||
locator.get<Logger>().d('$identifier ${toString()}');
|
locator.get<Logger>().d('$identifier ${toString()}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logInfo({String identifier = ''}) {
|
||||||
|
locator.get<Logger>().i('$identifier ${toString()}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void logError({
|
||||||
|
String identifier = '',
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
locator.get<Logger>().e(identifier, this, stackTrace ?? StackTrace.current);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
|||||||
const NotificationModePreference(),
|
const NotificationModePreference(),
|
||||||
const SwipeGesturePreference(),
|
const SwipeGesturePreference(),
|
||||||
const CollapseModePreference(),
|
const CollapseModePreference(),
|
||||||
NavigationModePreference(),
|
const NavigationModePreference(),
|
||||||
const ReaderModePreference(),
|
const ReaderModePreference(),
|
||||||
const MarkReadStoriesModePreference(),
|
const MarkReadStoriesModePreference(),
|
||||||
const EyeCandyModePreference(),
|
const EyeCandyModePreference(),
|
||||||
@ -54,8 +54,7 @@ abstract class IntPreference extends Preference<int> {
|
|||||||
const bool _notificationModeDefaultValue = true;
|
const bool _notificationModeDefaultValue = true;
|
||||||
const bool _swipeGestureModeDefaultValue = false;
|
const bool _swipeGestureModeDefaultValue = false;
|
||||||
const bool _displayModeDefaultValue = true;
|
const bool _displayModeDefaultValue = true;
|
||||||
const bool _navigationModeDefaultValueIOS = false;
|
const bool _navigationModeDefaultValue = false;
|
||||||
const bool _navigationModeDefaultValueAndroid = false;
|
|
||||||
const bool _eyeCandyModeDefaultValue = false;
|
const bool _eyeCandyModeDefaultValue = false;
|
||||||
const bool _trueDarkModeDefaultValue = false;
|
const bool _trueDarkModeDefaultValue = false;
|
||||||
const bool _readerModeDefaultValue = true;
|
const bool _readerModeDefaultValue = true;
|
||||||
@ -193,12 +192,9 @@ class StoryUrlModePreference extends BooleanPreference {
|
|||||||
/// The value deciding whether or not user should be
|
/// The value deciding whether or not user should be
|
||||||
/// navigated to web view first. Defaults to false.
|
/// navigated to web view first. Defaults to false.
|
||||||
class NavigationModePreference extends BooleanPreference {
|
class NavigationModePreference extends BooleanPreference {
|
||||||
NavigationModePreference({bool? val})
|
const NavigationModePreference({bool? val})
|
||||||
: super(
|
: super(
|
||||||
val: val ??
|
val: val ?? _navigationModeDefaultValue,
|
||||||
(Platform.isAndroid
|
|
||||||
? _navigationModeDefaultValueAndroid
|
|
||||||
: _navigationModeDefaultValueIOS),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -207,6 +207,23 @@ class PreferenceRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> clearAllFavs({required String username}) async {
|
||||||
|
final String key = _getFavKey(username);
|
||||||
|
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
await _syncedPrefs.setStringList(
|
||||||
|
key: key,
|
||||||
|
val: <String>[],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final SharedPreferences prefs = await _prefs;
|
||||||
|
await prefs.setStringList(
|
||||||
|
key,
|
||||||
|
<String>[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static String _getFavKey(String username) => 'fav_$username';
|
static String _getFavKey(String username) => 'fav_$username';
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||||
@ -25,16 +23,8 @@ class MorePopupMenu extends StatelessWidget {
|
|||||||
final bool isBlocked;
|
final bool isBlocked;
|
||||||
final VoidCallback onLoginTapped;
|
final VoidCallback onLoginTapped;
|
||||||
|
|
||||||
static double? _cachedStoryHeight;
|
static const double _storySheetHeight = 500;
|
||||||
static double? _cachedCommentHeight;
|
static const double _commentSheetHeight = 480;
|
||||||
|
|
||||||
static double get storyHeight {
|
|
||||||
return _cachedStoryHeight ??= Platform.isIOS ? 500 : 530;
|
|
||||||
}
|
|
||||||
|
|
||||||
static double get commentHeight {
|
|
||||||
return _cachedCommentHeight ??= Platform.isIOS ? 480 : 520;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -80,7 +70,7 @@ class MorePopupMenu extends StatelessWidget {
|
|||||||
final bool upvoted = voteState.vote == Vote.up;
|
final bool upvoted = voteState.vote == Vote.up;
|
||||||
final bool downvoted = voteState.vote == Vote.down;
|
final bool downvoted = voteState.vote == Vote.down;
|
||||||
return Container(
|
return Container(
|
||||||
height: item is Comment ? commentHeight : storyHeight,
|
height: item is Comment ? _commentSheetHeight : _storySheetHeight,
|
||||||
color: Theme.of(context).canvasColor,
|
color: Theme.of(context).canvasColor,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Palette.transparent,
|
color: Palette.transparent,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||||
|
import 'package:clipboard/clipboard.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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';
|
||||||
@ -21,7 +22,6 @@ import 'package:hacki/screens/profile/widgets/tab_bar_settings.dart';
|
|||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
import 'package:hacki/utils/utils.dart';
|
import 'package:hacki/utils/utils.dart';
|
||||||
import 'package:logger/logger.dart';
|
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
@ -192,7 +192,7 @@ class _SettingsState extends State<Settings> {
|
|||||||
.whereType<BooleanPreference>()
|
.whereType<BooleanPreference>()
|
||||||
.where(
|
.where(
|
||||||
(Preference<dynamic> e) => e.isDisplayable,
|
(Preference<dynamic> e) => e.isDisplayable,
|
||||||
))
|
)) ...<Widget>[
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(preference.title),
|
title: Text(preference.title),
|
||||||
subtitle: preference.subtitle.isNotEmpty
|
subtitle: preference.subtitle.isNotEmpty
|
||||||
@ -217,6 +217,8 @@ class _SettingsState extends State<Settings> {
|
|||||||
},
|
},
|
||||||
activeColor: Palette.orange,
|
activeColor: Palette.orange,
|
||||||
),
|
),
|
||||||
|
if (preference is StoryUrlModePreference) const Divider(),
|
||||||
|
],
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Font',
|
'Font',
|
||||||
@ -229,11 +231,24 @@ class _SettingsState extends State<Settings> {
|
|||||||
),
|
),
|
||||||
onTap: showThemeSettingDialog,
|
onTap: showThemeSettingDialog,
|
||||||
),
|
),
|
||||||
|
const Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Clear Data',
|
'Export Favorites',
|
||||||
),
|
),
|
||||||
onTap: showClearDataDialog,
|
onTap: onExportFavoritesTapped,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text(
|
||||||
|
'Clear Favorites',
|
||||||
|
),
|
||||||
|
onTap: showClearFavoritesDialog,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text(
|
||||||
|
'Clear Cache',
|
||||||
|
),
|
||||||
|
onTap: showClearCacheDialog,
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('About'),
|
title: const Text('About'),
|
||||||
@ -376,12 +391,12 @@ class _SettingsState extends State<Settings> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showClearDataDialog() {
|
void showClearCacheDialog() {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('Clear Data?'),
|
title: const Text('Clear Cache?'),
|
||||||
content: const Text(
|
content: const Text(
|
||||||
'Clear all cached images, stories and comments.',
|
'Clear all cached images, stories and comments.',
|
||||||
),
|
),
|
||||||
@ -621,11 +636,64 @@ class _SettingsState extends State<Settings> {
|
|||||||
LinkUtil.launchInExternalBrowser(Constants.githubIssueLink);
|
LinkUtil.launchInExternalBrowser(Constants.githubIssueLink);
|
||||||
}
|
}
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
locator.get<Logger>().e(
|
error.logError(stackTrace: stackTrace);
|
||||||
'Error caught in onGithubTapped',
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> onExportFavoritesTapped() async {
|
||||||
|
final List<int> allFavorites = context.read<FavCubit>().state.favIds;
|
||||||
|
|
||||||
|
if (allFavorites.isEmpty) {
|
||||||
|
showSnackBar(content: "You don't have any favorite item.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await FlutterClipboard.copy(
|
||||||
|
allFavorites.join('\n'),
|
||||||
|
).whenComplete(HapticFeedback.selectionClick);
|
||||||
|
showSnackBar(content: 'Ids of favorites have been copied to clipboard.');
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
error.logError(stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showClearFavoritesDialog() {
|
||||||
|
showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Remove all favorites?'),
|
||||||
|
content: const Text(
|
||||||
|
'''This will not effect favorites saved in your Hacker News account.''',
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text(
|
||||||
|
'Cancel',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
try {
|
||||||
|
context.read<FavCubit>().removeAll();
|
||||||
|
showSnackBar(content: 'All favorites have been removed.');
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
error.logError(stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'Confirm',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Palette.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
import 'package:memoize/memoize.dart';
|
||||||
|
|
||||||
class LinkView extends StatelessWidget {
|
class LinkView extends StatelessWidget {
|
||||||
const LinkView({
|
LinkView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.metadata,
|
required this.metadata,
|
||||||
required this.url,
|
required this.url,
|
||||||
@ -13,18 +15,19 @@ class LinkView extends StatelessWidget {
|
|||||||
required this.description,
|
required this.description,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
required this.showMetadata,
|
required this.showMetadata,
|
||||||
required this.showUrl,
|
required bool showUrl,
|
||||||
|
required this.bodyMaxLines,
|
||||||
this.imageUri,
|
this.imageUri,
|
||||||
this.imagePath,
|
this.imagePath,
|
||||||
this.titleTextStyle,
|
this.titleTextStyle,
|
||||||
this.bodyTextStyle,
|
this.bodyTextStyle,
|
||||||
this.showMultiMedia = true,
|
this.showMultiMedia = true,
|
||||||
this.bodyTextOverflow,
|
this.bodyTextOverflow,
|
||||||
this.bodyMaxLines,
|
|
||||||
this.isIcon = false,
|
this.isIcon = false,
|
||||||
this.bgColor,
|
this.bgColor,
|
||||||
this.radius = 0,
|
this.radius = 0,
|
||||||
}) : assert(
|
}) : showUrl = showUrl && url.isNotEmpty,
|
||||||
|
assert(
|
||||||
!showMultiMedia ||
|
!showMultiMedia ||
|
||||||
(showMultiMedia && (imageUri != null || imagePath != null)),
|
(showMultiMedia && (imageUri != null || imagePath != null)),
|
||||||
'imageUri or imagePath cannot be null when showMultiMedia is true',
|
'imageUri or imagePath cannot be null when showMultiMedia is true',
|
||||||
@ -42,14 +45,17 @@ class LinkView extends StatelessWidget {
|
|||||||
final TextStyle? bodyTextStyle;
|
final TextStyle? bodyTextStyle;
|
||||||
final bool showMultiMedia;
|
final bool showMultiMedia;
|
||||||
final TextOverflow? bodyTextOverflow;
|
final TextOverflow? bodyTextOverflow;
|
||||||
final int? bodyMaxLines;
|
final int bodyMaxLines;
|
||||||
final bool isIcon;
|
final bool isIcon;
|
||||||
final double radius;
|
final double radius;
|
||||||
final Color? bgColor;
|
final Color? bgColor;
|
||||||
final bool showMetadata;
|
final bool showMetadata;
|
||||||
final bool showUrl;
|
final bool showUrl;
|
||||||
|
|
||||||
double computeTitleFontSize(double width) {
|
static final double Function(double) _getTitleFontSize =
|
||||||
|
memo1(_computeTitleFontSize);
|
||||||
|
|
||||||
|
static double _computeTitleFontSize(double width) {
|
||||||
double size = width * 0.13;
|
double size = width * 0.13;
|
||||||
if (size > 15) {
|
if (size > 15) {
|
||||||
size = 15;
|
size = 15;
|
||||||
@ -57,16 +63,26 @@ class LinkView extends StatelessWidget {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
int computeTitleLines(double layoutHeight) {
|
static final int Function(double) _getTitleLines = memo1(_computeTitleLines);
|
||||||
|
|
||||||
|
static int _computeTitleLines(double layoutHeight) {
|
||||||
return layoutHeight >= 100 ? 2 : 1;
|
return layoutHeight >= 100 ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int computeBodyLines(double layoutHeight) {
|
static final int Function(int, bool, bool, String?) _getBodyLines =
|
||||||
int lines = 1;
|
memo4(_computeBodyLines);
|
||||||
if (layoutHeight > 40) {
|
|
||||||
lines += (layoutHeight - 40.0) ~/ 15.0;
|
static int _computeBodyLines(
|
||||||
}
|
int bodyMaxLines,
|
||||||
return lines;
|
bool showMetadata,
|
||||||
|
bool showUrl,
|
||||||
|
String? fontFamily,
|
||||||
|
) {
|
||||||
|
final int maxLines = bodyMaxLines -
|
||||||
|
(showMetadata ? 1 : 0) -
|
||||||
|
(showUrl ? 1 : 0) +
|
||||||
|
(fontFamily == Font.ubuntuMono.name ? 1 : 0);
|
||||||
|
return maxLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -76,15 +92,15 @@ 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 titleFontStyle = titleTextStyle ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
fontSize: computeTitleFontSize(layoutWidth),
|
fontSize: _getTitleFontSize(layoutWidth),
|
||||||
color: Palette.black,
|
color: Palette.black,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
);
|
);
|
||||||
final TextStyle bodyFontSize = bodyTextStyle ??
|
final TextStyle bodyFontStyle = bodyTextStyle ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
fontSize: computeTitleFontSize(layoutWidth) - 1,
|
fontSize: _getTitleFontSize(layoutWidth) - 1,
|
||||||
color: Palette.grey,
|
color: Palette.grey,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
);
|
);
|
||||||
@ -96,7 +112,7 @@ class LinkView extends StatelessWidget {
|
|||||||
if (showMultiMedia)
|
if (showMultiMedia)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
right: 5,
|
right: 8,
|
||||||
top: 5,
|
top: 5,
|
||||||
bottom: 5,
|
bottom: 5,
|
||||||
),
|
),
|
||||||
@ -112,7 +128,7 @@ class LinkView extends StatelessWidget {
|
|||||||
imageUrl: imageUri!,
|
imageUrl: imageUri!,
|
||||||
fit: isIcon ? BoxFit.scaleDown : BoxFit.fitWidth,
|
fit: isIcon ? BoxFit.scaleDown : BoxFit.fitWidth,
|
||||||
memCacheHeight: layoutHeight.toInt() * 4,
|
memCacheHeight: layoutHeight.toInt() * 4,
|
||||||
errorWidget: (BuildContext context, _, dynamic __) {
|
errorWidget: (BuildContext context, _, __) {
|
||||||
return Image.asset(
|
return Image.asset(
|
||||||
Constants.hackerNewsLogoPath,
|
Constants.hackerNewsLogoPath,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@ -124,22 +140,85 @@ class LinkView extends StatelessWidget {
|
|||||||
else
|
else
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 4,
|
child: Column(
|
||||||
child: Padding(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
children: <Widget>[
|
||||||
child: Column(
|
Padding(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
padding: EdgeInsets.only(
|
||||||
children: <Widget>[
|
top: Theme.of(context)
|
||||||
_buildTitleContainer(
|
.textTheme
|
||||||
titleFontSize,
|
.bodyMedium
|
||||||
computeTitleLines(layoutHeight),
|
?.fontFamily ==
|
||||||
|
Font.robotoSlab.name
|
||||||
|
? 2
|
||||||
|
: 4,
|
||||||
),
|
),
|
||||||
_buildBodyContainer(
|
child: Column(
|
||||||
bodyFontSize,
|
children: <Widget>[
|
||||||
computeBodyLines(layoutHeight),
|
Container(
|
||||||
)
|
alignment: Alignment.topLeft,
|
||||||
],
|
child: Text(
|
||||||
),
|
title,
|
||||||
|
style: titleFontStyle,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: _getTitleLines(layoutHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showUrl && url.isNotEmpty)
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(
|
||||||
|
'($readableUrl)',
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: titleFontStyle.copyWith(
|
||||||
|
color: Palette.grey,
|
||||||
|
fontSize: titleFontStyle.fontSize == null
|
||||||
|
? 12
|
||||||
|
: titleFontStyle.fontSize! - 4,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
overflow:
|
||||||
|
bodyTextOverflow ?? TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showMetadata)
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
margin: const EdgeInsets.only(top: 2),
|
||||||
|
child: Text(
|
||||||
|
metadata,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: bodyFontStyle.copyWith(
|
||||||
|
fontSize: bodyFontStyle.fontSize == null
|
||||||
|
? 12
|
||||||
|
: bodyFontStyle.fontSize! - 2,
|
||||||
|
),
|
||||||
|
overflow: bodyTextOverflow ?? TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(
|
||||||
|
description,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: bodyFontStyle,
|
||||||
|
overflow: bodyTextOverflow ?? TextOverflow.ellipsis,
|
||||||
|
maxLines: _getBodyLines(
|
||||||
|
bodyMaxLines,
|
||||||
|
showMetadata,
|
||||||
|
showUrl,
|
||||||
|
Theme.of(context).textTheme.bodyMedium?.fontFamily,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -148,81 +227,4 @@ class LinkView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTitleContainer(TextStyle titleTS, int maxLines) {
|
|
||||||
final bool showUrl = this.showUrl && url.isNotEmpty;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(4, 2, 3, 0),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: titleTS,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: maxLines,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (showUrl)
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Text(
|
|
||||||
'($readableUrl)',
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: titleTS.copyWith(
|
|
||||||
color: Palette.grey,
|
|
||||||
fontSize:
|
|
||||||
titleTS.fontSize == null ? 12 : titleTS.fontSize! - 4,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
overflow: bodyTextOverflow ?? TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildBodyContainer(TextStyle bodyTS, int maxLines) {
|
|
||||||
return Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(5, 2, 5, 0),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
if (showMetadata)
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Text(
|
|
||||||
metadata,
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: bodyTS.copyWith(
|
|
||||||
fontSize:
|
|
||||||
bodyTS.fontSize == null ? 12 : bodyTS.fontSize! - 2,
|
|
||||||
),
|
|
||||||
overflow: bodyTextOverflow ?? TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Text(
|
|
||||||
description,
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: bodyTS,
|
|
||||||
overflow: bodyTextOverflow ?? TextOverflow.ellipsis,
|
|
||||||
maxLines: (bodyMaxLines ?? maxLines) -
|
|
||||||
(showMetadata ? 1 : 0) -
|
|
||||||
(showUrl && url.isNotEmpty ? 1 : 0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -600,6 +600,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
|
memoize:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: memoize
|
||||||
|
sha256: "51481d328c86cbdc59711369179bac88551ca0556569249be5317e66fc796cac"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 1.2.4+97
|
version: 1.3.0+99
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@ -45,6 +45,7 @@ dependencies:
|
|||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
linkify: ^4.1.0
|
linkify: ^4.1.0
|
||||||
logger: ^1.1.0
|
logger: ^1.1.0
|
||||||
|
memoize: ^3.0.0
|
||||||
package_info_plus: ^3.0.3
|
package_info_plus: ^3.0.3
|
||||||
path: ^1.8.2
|
path: ^1.8.2
|
||||||
path_provider: ^2.0.12
|
path_provider: ^2.0.12
|
||||||
|
Reference in New Issue
Block a user