Compare commits

...

3 Commits

Author SHA1 Message Date
aa6a2c684c bugfixes. (#181) 2023-03-01 12:24:16 -08:00
d4778d9530 remove bottom padding. (#178) 2023-02-28 15:29:03 -08:00
c702e08481 allow exporting favorites to clipboard. (#177) 2023-02-28 14:54:51 -08:00
13 changed files with 136 additions and 51 deletions

View File

@ -50,7 +50,7 @@ android {
defaultConfig {
applicationId "com.jiaqifeng.hacki"
minSdkVersion 26
minSdkVersion 30
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@ -64,12 +64,15 @@ android {
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
}
}
}
flutter {

View File

@ -37,15 +37,6 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View File

@ -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) {
emit(
state.copyWith(

View File

@ -5,4 +5,15 @@ extension ObjectExtension on Object {
void log({String identifier = ''}) {
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);
}
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/screens/widgets/custom_linkify/linkifiers/linkifiers.dart';
import 'package:hacki/screens/widgets/custom_linkify/custom_linkify.dart';
import 'package:hacki/utils/utils.dart';
extension WidgetModifier on Widget {

View File

@ -31,7 +31,7 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
const NotificationModePreference(),
const SwipeGesturePreference(),
const CollapseModePreference(),
NavigationModePreference(),
const NavigationModePreference(),
const ReaderModePreference(),
const MarkReadStoriesModePreference(),
const EyeCandyModePreference(),
@ -54,8 +54,7 @@ abstract class IntPreference extends Preference<int> {
const bool _notificationModeDefaultValue = true;
const bool _swipeGestureModeDefaultValue = false;
const bool _displayModeDefaultValue = true;
const bool _navigationModeDefaultValueIOS = false;
const bool _navigationModeDefaultValueAndroid = false;
const bool _navigationModeDefaultValue = false;
const bool _eyeCandyModeDefaultValue = false;
const bool _trueDarkModeDefaultValue = false;
const bool _readerModeDefaultValue = true;
@ -193,12 +192,9 @@ class StoryUrlModePreference extends BooleanPreference {
/// The value deciding whether or not user should be
/// navigated to web view first. Defaults to false.
class NavigationModePreference extends BooleanPreference {
NavigationModePreference({bool? val})
const NavigationModePreference({bool? val})
: super(
val: val ??
(Platform.isAndroid
? _navigationModeDefaultValueAndroid
: _navigationModeDefaultValueIOS),
val: val ?? _navigationModeDefaultValue,
);
@override

View File

@ -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';
//#endregion

View File

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
@ -25,16 +23,8 @@ class MorePopupMenu extends StatelessWidget {
final bool isBlocked;
final VoidCallback onLoginTapped;
static double? _cachedStoryHeight;
static double? _cachedCommentHeight;
static double get storyHeight {
return _cachedStoryHeight ??= Platform.isIOS ? 500 : 530;
}
static double get commentHeight {
return _cachedCommentHeight ??= Platform.isIOS ? 480 : 520;
}
static const double _storySheetHeight = 500;
static const double _commentSheetHeight = 480;
@override
Widget build(BuildContext context) {
@ -80,7 +70,7 @@ class MorePopupMenu extends StatelessWidget {
final bool upvoted = voteState.vote == Vote.up;
final bool downvoted = voteState.vote == Vote.down;
return Container(
height: item is Comment ? commentHeight : storyHeight,
height: item is Comment ? _commentSheetHeight : _storySheetHeight,
color: Theme.of(context).canvasColor,
child: Material(
color: Palette.transparent,

View File

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:clipboard/clipboard.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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/styles/styles.dart';
import 'package:hacki/utils/utils.dart';
import 'package:logger/logger.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
@ -192,7 +192,7 @@ class _SettingsState extends State<Settings> {
.whereType<BooleanPreference>()
.where(
(Preference<dynamic> e) => e.isDisplayable,
))
)) ...<Widget>[
SwitchListTile(
title: Text(preference.title),
subtitle: preference.subtitle.isNotEmpty
@ -217,6 +217,8 @@ class _SettingsState extends State<Settings> {
},
activeColor: Palette.orange,
),
if (preference is StoryUrlModePreference) const Divider(),
],
ListTile(
title: const Text(
'Font',
@ -229,11 +231,24 @@ class _SettingsState extends State<Settings> {
),
onTap: showThemeSettingDialog,
),
const Divider(),
ListTile(
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(
title: const Text('About'),
@ -376,12 +391,12 @@ class _SettingsState extends State<Settings> {
);
}
void showClearDataDialog() {
void showClearCacheDialog() {
showDialog<void>(
context: context,
builder: (_) {
return AlertDialog(
title: const Text('Clear Data?'),
title: const Text('Clear Cache?'),
content: const Text(
'Clear all cached images, stories and comments.',
),
@ -411,7 +426,7 @@ class _SettingsState extends State<Settings> {
DefaultCacheManager().emptyCache,
)
.whenComplete(() {
showSnackBar(content: 'Data cleared!');
showSnackBar(content: 'Cache cleared!');
});
},
child: const Text(
@ -621,11 +636,64 @@ class _SettingsState extends State<Settings> {
LinkUtil.launchInExternalBrowser(Constants.githubIssueLink);
}
} catch (error, stackTrace) {
locator.get<Logger>().e(
'Error caught in onGithubTapped',
error,
stackTrace,
);
error.logError(stackTrace: 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,
),
),
),
],
);
},
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:hacki/styles/palette.dart';
import 'package:hacki/utils/utils.dart';
import 'package:linkify/linkify.dart';
export 'package:hacki/screens/widgets/custom_linkify/linkifiers/linkifiers.dart';
export 'package:linkify/linkify.dart'
show
LinkifyElement,
@ -27,7 +28,7 @@ class Linkify extends StatelessWidget {
required this.text,
this.linkifiers = defaultLinkifiers,
this.onOpen,
this.options = const LinkifyOptions(),
this.options = LinkifierUtil.linkifyOptions,
// TextSpan
this.style,
this.linkStyle,
@ -154,7 +155,7 @@ class SelectableLinkify extends StatelessWidget {
required this.text,
this.linkifiers = defaultLinkifiers,
this.onOpen,
this.options = const LinkifyOptions(),
this.options = LinkifierUtil.linkifyOptions,
// TextSpan
this.style,
this.linkStyle,

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:linkify/linkify.dart';
final RegExp _quoteRegex = RegExp(
r'(?=^> )(.*?)(?=\n|$)',
r'(?=^>)(.*?)(?=\n|$)',
multiLine: true,
);

View File

@ -2,8 +2,9 @@ import 'package:hacki/screens/widgets/custom_linkify/linkifiers/linkifiers.dart'
import 'package:linkify/linkify.dart';
abstract class LinkifierUtil {
static const LinkifyOptions linkifyOptions = LinkifyOptions(humanize: false);
static List<LinkifyElement> linkify(String text) {
const LinkifyOptions options = LinkifyOptions();
const List<Linkifier> linkifiers = <Linkifier>[
UrlLinkifier(),
EmailLinkifier(),
@ -21,7 +22,7 @@ abstract class LinkifierUtil {
}
for (final Linkifier linkifier in linkifiers) {
list = linkifier.parse(list, options);
list = linkifier.parse(list, linkifyOptions);
}
return list;

View File

@ -1,6 +1,6 @@
name: hacki
description: A Hacker News reader.
version: 1.2.5+98
version: 1.3.1+100
publish_to: none
environment: