Compare commits

...

3 Commits

Author SHA1 Message Date
6a8022e177 fixed android build. 2022-05-15 17:38:18 -07:00
4626b6f45b v0.2.6 (#18)
* minor fixes.

* updated feature discovery.

* fixed theme.
2022-05-15 01:45:53 -07:00
674c67a317 updated README.md 2022-05-13 12:06:39 -07:00
12 changed files with 72 additions and 50 deletions

View File

@ -1,7 +1,7 @@
# <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 reader made with Flutter that is just enough.
A simple noiseless [Hacker News](https://news.ycombinator.com/) reader made with Flutter that is just enough.
[![App Store](https://img.shields.io/itunes/v/1602043763?label=App%20Store)](https://apps.apple.com/us/app/hacki/id1602043763)
[![Play Store](https://img.shields.io/badge/Play%20Store--yellow)](https://play.google.com/store/apps/details?id=com.jiaqifeng.hacki&hl=en_US&gl=US)

View File

@ -0,0 +1,4 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -367,7 +367,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 11;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -376,7 +376,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.2.5;
MARKETING_VERSION = 0.2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -503,7 +503,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 11;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -512,7 +512,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.2.5;
MARKETING_VERSION = 0.2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -533,7 +533,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 11;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -542,7 +542,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.2.5;
MARKETING_VERSION = 0.2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -4,17 +4,20 @@ import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:feature_discovery/feature_discovery.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:hacki/blocs/blocs.dart';
import 'package:hacki/config/custom_router.dart';
import 'package:hacki/config/locator.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/repositories/repositories.dart' show PreferenceRepository;
import 'package:hacki/screens/screens.dart';
import 'package:hacki/services/fetcher.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:workmanager/workmanager.dart';
// For receiving payload event from local notifications.
@ -63,6 +66,9 @@ Future<void> main() async {
await setUpLocator();
final AdaptiveThemeMode? savedThemeMode = await AdaptiveTheme.getThemeMode();
final SharedPreferences prefs = await SharedPreferences.getInstance();
final bool trueDarkMode =
prefs.getBool(PreferenceRepository.trueDarkModeKey) ?? false;
// Uncomment code below for running with logging.
// BlocOverrides.runZoned(
@ -79,6 +85,7 @@ Future<void> main() async {
runApp(
HackiApp(
savedThemeMode: savedThemeMode,
trueDarkMode: trueDarkMode,
),
);
}
@ -87,9 +94,11 @@ class HackiApp extends StatelessWidget {
const HackiApp({
super.key,
this.savedThemeMode,
required this.trueDarkMode,
});
final AdaptiveThemeMode? savedThemeMode;
final bool trueDarkMode;
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
@ -166,6 +175,7 @@ class HackiApp extends StatelessWidget {
dark: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.orange,
canvasColor: trueDarkMode ? Colors.black : null,
),
initial: savedThemeMode ?? AdaptiveThemeMode.system,
builder: (ThemeData theme, ThemeData darkTheme) {
@ -174,20 +184,35 @@ class HackiApp extends StatelessWidget {
primarySwatch: Colors.orange,
canvasColor: Colors.black,
);
return BlocBuilder<PreferenceCubit, PreferenceState>(
buildWhen: (PreferenceState previous, PreferenceState current) =>
previous.useTrueDark != current.useTrueDark,
builder: (BuildContext context, PreferenceState prefState) {
return FeatureDiscovery(
child: MaterialApp(
title: 'Hacki',
debugShowCheckedModeBanner: false,
theme: prefState.useTrueDark ? trueDarkTheme : theme,
darkTheme: prefState.useTrueDark ? trueDarkTheme : darkTheme,
navigatorKey: navigatorKey,
onGenerateRoute: CustomRouter.onGenerateRoute,
initialRoute: HomeScreen.routeName,
),
return FutureBuilder<AdaptiveThemeMode?>(
future: AdaptiveTheme.getThemeMode(),
builder: (
BuildContext context,
AsyncSnapshot<AdaptiveThemeMode?> snapshot,
) {
final AdaptiveThemeMode? mode = snapshot.data;
return BlocBuilder<PreferenceCubit, PreferenceState>(
buildWhen:
(PreferenceState previous, PreferenceState current) =>
previous.useTrueDark != current.useTrueDark,
builder: (BuildContext context, PreferenceState prefState) {
final bool useTrueDark = prefState.useTrueDark &&
(mode == AdaptiveThemeMode.dark ||
(mode == AdaptiveThemeMode.system &&
SchedulerBinding
.instance.window.platformBrightness ==
Brightness.dark));
return FeatureDiscovery(
child: MaterialApp(
title: 'Hacki',
debugShowCheckedModeBanner: false,
theme: useTrueDark ? trueDarkTheme : theme,
navigatorKey: navigatorKey,
onGenerateRoute: CustomRouter.onGenerateRoute,
initialRoute: HomeScreen.routeName,
),
);
},
);
},
);

View File

@ -22,9 +22,11 @@ class PreferenceRepository {
static const String _lastReadStoryIdKey = 'lastReadStoryId';
static const String _notificationModeKey = 'notificationMode';
static const String _trueDarkModeKey = 'trueDarkMode';
static const String _readerModeKey = 'readerMode';
/// Exposing this val for main func.
static const String trueDarkModeKey = 'trueDarkMode';
/// The key of a boolean value deciding whether or not the story
/// tile should display link preview. Defaults to true.
static const String _displayModeKey = 'displayMode';
@ -99,7 +101,7 @@ class PreferenceRepository {
Future<bool> get trueDarkMode async => _prefs.then(
(SharedPreferences prefs) =>
prefs.getBool(_trueDarkModeKey) ?? _trueDarkModeDefaultValue,
prefs.getBool(trueDarkModeKey) ?? _trueDarkModeDefaultValue,
);
Future<bool> get readerMode async => _prefs.then(
@ -247,8 +249,8 @@ class PreferenceRepository {
Future<void> toggleTrueDarkMode() async {
final SharedPreferences prefs = await _prefs;
final bool currentMode =
prefs.getBool(_trueDarkModeKey) ?? _trueDarkModeDefaultValue;
await prefs.setBool(_trueDarkModeKey, !currentMode);
prefs.getBool(trueDarkModeKey) ?? _trueDarkModeDefaultValue;
await prefs.setBool(trueDarkModeKey, !currentMode);
}
Future<void> toggleReaderMode() async {

View File

@ -372,6 +372,7 @@ class _HomeScreenState extends State<HomeScreen>
}
Future<bool> onFeatureDiscoveryDismissed() {
HapticFeedback.lightImpact();
ScaffoldMessenger.of(context).clearSnackBars();
showSnackBar(content: 'Tap on icon to continue');
return Future<bool>.value(false);
@ -383,7 +384,7 @@ class _HomeScreenState extends State<HomeScreen>
final bool useReader = context.read<PreferenceCubit>().state.useReader;
final bool offlineReading =
context.read<StoriesBloc>().state.offlineReading;
final bool firstTimeReading = cacheService.isFirstTimeReading(story.id);
final bool hasRead = context.read<StoriesBloc>().hasRead(story);
final bool splitViewEnabled = context.read<SplitViewCubit>().state.enabled;
// If a story is a job story and it has a link to the job posting,
@ -411,8 +412,7 @@ class _HomeScreenState extends State<HomeScreen>
}
}
if (!offlineReading &&
(isJobWithLink || (showWebFirst && firstTimeReading))) {
if (!offlineReading && (isJobWithLink || (showWebFirst && !hasRead))) {
LinkUtil.launchUrl(story.url, useReader: useReader);
cacheService.store(story.id);
}

View File

@ -357,7 +357,9 @@ class _ProfileScreenState extends State<ProfileScreen>
),
SwitchListTile(
title: const Text('True Dark Mode'),
subtitle: const Text('real dark.'),
subtitle: const Text(
'you might need to restart the app.',
),
value: preferenceState.useTrueDark,
onChanged: (bool val) {
HapticFeedback.lightImpact();
@ -368,17 +370,10 @@ class _ProfileScreenState extends State<ProfileScreen>
activeColor: Colors.orange,
),
ListTile(
title: Text(
title: const Text(
'Theme',
style: TextStyle(
decoration: preferenceState.useTrueDark
? TextDecoration.lineThrough
: null,
),
),
onTap: () => showThemeSettingDialog(
useTrueDarkMode: preferenceState.useTrueDark,
),
onTap: showThemeSettingDialog,
),
ListTile(
title: const Text('About'),
@ -388,7 +383,7 @@ class _ProfileScreenState extends State<ProfileScreen>
showAboutDialog(
context: context,
applicationName: 'Hacki',
applicationVersion: 'v0.2.5',
applicationVersion: 'v0.2.6',
applicationIcon: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(12),
@ -577,13 +572,7 @@ class _ProfileScreenState extends State<ProfileScreen>
);
}
void showThemeSettingDialog({bool useTrueDarkMode = false}) {
if (useTrueDarkMode) {
showSnackBar(
content: "Can't choose theme when using true dark mode.",
);
return;
}
void showThemeSettingDialog() {
showDialog<void>(
context: context,
builder: (_) {

View File

@ -559,6 +559,7 @@ class _StoryScreenState extends State<StoryScreen> {
}
Future<bool> onFeatureDiscoveryDismissed() {
HapticFeedback.lightImpact();
ScaffoldMessenger.of(context).clearSnackBars();
showSnackBar(content: 'Tap on icon to continue');
return Future<bool>.value(false);

View File

@ -181,7 +181,7 @@ class CommentTile extends StatelessWidget {
else if (state.collapsed)
const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 12),
padding: EdgeInsets.only(bottom: 8),
child: Text(
'collapsed',
style:

View File

@ -1231,7 +1231,7 @@ packages:
name: workmanager
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.1"
version: "0.5.0"
xdg_directories:
dependency: transitive
description:

View File

@ -1,6 +1,6 @@
name: hacki
description: A Hacker News reader.
version: 0.2.5+39
version: 0.2.6+40
publish_to: none
environment:
@ -56,7 +56,7 @@ dependencies:
universal_platform: ^1.0.0+1
url_launcher: ^6.0.10
wakelock: ^0.6.1+2
workmanager: ^0.4.1
workmanager: ^0.5.0
dev_dependencies:
bloc_test: ^9.0.3

View File

@ -9,6 +9,7 @@ void main() {
await tester.pumpWidget(
const HackiApp(
savedThemeMode: AdaptiveThemeMode.light,
trueDarkMode: false,
),
);