mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
e15dcba93b | |||
1362b93a74 | |||
ac18793f98 | |||
e52f65c773 |
@ -7,7 +7,7 @@ abstract class Constants {
|
|||||||
'https://github.com/Livinglist/Hacki/blob/master/assets/privacy_policy.md';
|
'https://github.com/Livinglist/Hacki/blob/master/assets/privacy_policy.md';
|
||||||
static const String hackerNewsLogoLink =
|
static const String hackerNewsLogoLink =
|
||||||
'https://pbs.twimg.com/profile_images/469397708986269696/iUrYEOpJ_400x400.png';
|
'https://pbs.twimg.com/profile_images/469397708986269696/iUrYEOpJ_400x400.png';
|
||||||
static const String portfolioLink = 'https://livinglist.github.io';
|
static const String portfolioLink = 'https://github.com/Livinglist';
|
||||||
static const String githubLink = 'https://github.com/Livinglist/Hacki';
|
static const String githubLink = 'https://github.com/Livinglist/Hacki';
|
||||||
static const String appStoreLink =
|
static const String appStoreLink =
|
||||||
'https://apps.apple.com/us/app/hacki/id1602043763?action=write-review';
|
'https://apps.apple.com/us/app/hacki/id1602043763?action=write-review';
|
||||||
|
@ -52,8 +52,6 @@ class PreferenceState extends Equatable {
|
|||||||
|
|
||||||
bool get complexStoryTileEnabled => _isOn<DisplayModePreference>();
|
bool get complexStoryTileEnabled => _isOn<DisplayModePreference>();
|
||||||
|
|
||||||
bool get webFirstEnabled => _isOn<NavigationModePreference>();
|
|
||||||
|
|
||||||
bool get eyeCandyEnabled => _isOn<EyeCandyModePreference>();
|
bool get eyeCandyEnabled => _isOn<EyeCandyModePreference>();
|
||||||
|
|
||||||
bool get trueDarkEnabled => _isOn<TrueDarkModePreference>();
|
bool get trueDarkEnabled => _isOn<TrueDarkModePreference>();
|
||||||
|
@ -31,7 +31,6 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
|||||||
const NotificationModePreference(),
|
const NotificationModePreference(),
|
||||||
const SwipeGesturePreference(),
|
const SwipeGesturePreference(),
|
||||||
const CollapseModePreference(),
|
const CollapseModePreference(),
|
||||||
const NavigationModePreference(),
|
|
||||||
const ReaderModePreference(),
|
const ReaderModePreference(),
|
||||||
const MarkReadStoriesModePreference(),
|
const MarkReadStoriesModePreference(),
|
||||||
const EyeCandyModePreference(),
|
const EyeCandyModePreference(),
|
||||||
@ -54,7 +53,6 @@ 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 _navigationModeDefaultValue = 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;
|
||||||
@ -189,29 +187,6 @@ class StoryUrlModePreference extends BooleanPreference {
|
|||||||
String get subtitle => '''show url in story tile.''';
|
String get subtitle => '''show url in story tile.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The value deciding whether or not user should be
|
|
||||||
/// navigated to web view first. Defaults to false.
|
|
||||||
class NavigationModePreference extends BooleanPreference {
|
|
||||||
const NavigationModePreference({bool? val})
|
|
||||||
: super(
|
|
||||||
val: val ?? _navigationModeDefaultValue,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
NavigationModePreference copyWith({required bool? val}) {
|
|
||||||
return NavigationModePreference(val: val);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get key => 'navigationMode';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get title => 'Show Web Page First';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get subtitle => '''show web page first after tapping on story.''';
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReaderModePreference extends BooleanPreference {
|
class ReaderModePreference extends BooleanPreference {
|
||||||
const ReaderModePreference({bool? val})
|
const ReaderModePreference({bool? val})
|
||||||
: super(val: val ?? _readerModeDefaultValue);
|
: super(val: val ?? _readerModeDefaultValue);
|
||||||
|
@ -210,12 +210,9 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onStoryTapped(Story story, {bool isPin = false}) {
|
void onStoryTapped(Story story, {bool isPin = false}) {
|
||||||
final bool showWebFirst =
|
|
||||||
context.read<PreferenceCubit>().state.webFirstEnabled;
|
|
||||||
final bool useReader = context.read<PreferenceCubit>().state.readerEnabled;
|
final bool useReader = context.read<PreferenceCubit>().state.readerEnabled;
|
||||||
final bool offlineReading =
|
final bool offlineReading =
|
||||||
context.read<StoriesBloc>().state.isOfflineReading;
|
context.read<StoriesBloc>().state.isOfflineReading;
|
||||||
final bool hasRead = isPin || context.read<StoriesBloc>().hasRead(story);
|
|
||||||
final bool splitViewEnabled = context.read<SplitViewCubit>().state.enabled;
|
final bool splitViewEnabled = context.read<SplitViewCubit>().state.enabled;
|
||||||
|
|
||||||
// If a story is a job story and it has a link to the job posting,
|
// If a story is a job story and it has a link to the job posting,
|
||||||
@ -245,7 +242,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (story.url.isNotEmpty && (isJobWithLink || (showWebFirst && !hasRead))) {
|
if (story.url.isNotEmpty && isJobWithLink) {
|
||||||
LinkUtil.launch(
|
LinkUtil.launch(
|
||||||
story.url,
|
story.url,
|
||||||
useReader: useReader,
|
useReader: useReader,
|
||||||
|
@ -7,13 +7,13 @@ import 'package:hacki/models/models.dart';
|
|||||||
import 'package:hacki/screens/widgets/link_preview/link_view.dart';
|
import 'package:hacki/screens/widgets/link_preview/link_view.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:url_launcher/url_launcher.dart';
|
|
||||||
|
|
||||||
class LinkPreview extends StatefulWidget {
|
class LinkPreview extends StatefulWidget {
|
||||||
const LinkPreview({
|
const LinkPreview({
|
||||||
super.key,
|
super.key,
|
||||||
required this.link,
|
required this.link,
|
||||||
required this.story,
|
required this.story,
|
||||||
|
required this.onTap,
|
||||||
required this.showMetadata,
|
required this.showMetadata,
|
||||||
required this.showUrl,
|
required this.showUrl,
|
||||||
required this.isOfflineReading,
|
required this.isOfflineReading,
|
||||||
@ -34,6 +34,7 @@ class LinkPreview extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final Story story;
|
final Story story;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
/// Web address (Url that need to be parsed)
|
/// Web address (Url that need to be parsed)
|
||||||
/// For IOS & Web, only HTTP and HTTPS are support
|
/// For IOS & Web, only HTTP and HTTPS are support
|
||||||
@ -141,19 +142,6 @@ class _LinkPreviewState extends State<LinkPreview> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _launchURL(String url) async {
|
|
||||||
final Uri uri = Uri.parse(url);
|
|
||||||
if (await canLaunchUrl(uri)) {
|
|
||||||
await launchUrl(uri);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await launchUrl(uri);
|
|
||||||
} catch (err) {
|
|
||||||
throw Exception('Could not launch $url. Error: $err');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildLinkContainer(
|
Widget _buildLinkContainer(
|
||||||
double height, {
|
double height, {
|
||||||
String? title = '',
|
String? title = '',
|
||||||
@ -184,7 +172,7 @@ class _LinkPreviewState extends State<LinkPreview> {
|
|||||||
description: desc ?? title ?? 'no comment yet.',
|
description: desc ?? title ?? 'no comment yet.',
|
||||||
imageUri: imageUri,
|
imageUri: imageUri,
|
||||||
imagePath: Constants.hackerNewsLogoPath,
|
imagePath: Constants.hackerNewsLogoPath,
|
||||||
onTap: _launchURL,
|
onTap: widget.onTap,
|
||||||
titleTextStyle: widget.titleStyle,
|
titleTextStyle: widget.titleStyle,
|
||||||
bodyTextOverflow: widget.bodyTextOverflow,
|
bodyTextOverflow: widget.bodyTextOverflow,
|
||||||
bodyMaxLines: widget.bodyMaxLines,
|
bodyMaxLines: widget.bodyMaxLines,
|
||||||
|
@ -5,7 +5,9 @@ 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/models/models.dart';
|
||||||
import 'package:hacki/screens/widgets/link_preview/models/models.dart';
|
import 'package:hacki/screens/widgets/link_preview/models/models.dart';
|
||||||
|
import 'package:hacki/screens/widgets/tap_down_wrapper.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
import 'package:hacki/utils/link_util.dart';
|
||||||
|
|
||||||
class LinkView extends StatelessWidget {
|
class LinkView extends StatelessWidget {
|
||||||
LinkView({
|
LinkView({
|
||||||
@ -41,7 +43,7 @@ class LinkView extends StatelessWidget {
|
|||||||
final String description;
|
final String description;
|
||||||
final String? imageUri;
|
final String? imageUri;
|
||||||
final String? imagePath;
|
final String? imagePath;
|
||||||
final void Function(String) onTap;
|
final VoidCallback onTap;
|
||||||
final TextStyle titleTextStyle;
|
final TextStyle titleTextStyle;
|
||||||
final bool showMultiMedia;
|
final bool showMultiMedia;
|
||||||
final TextOverflow? bodyTextOverflow;
|
final TextOverflow? bodyTextOverflow;
|
||||||
@ -176,17 +178,26 @@ class LinkView extends StatelessWidget {
|
|||||||
titleStyle,
|
titleStyle,
|
||||||
);
|
);
|
||||||
|
|
||||||
return InkWell(
|
return Row(
|
||||||
onTap: () => onTap(url),
|
children: <Widget>[
|
||||||
child: Row(
|
if (showMultiMedia)
|
||||||
children: <Widget>[
|
Padding(
|
||||||
if (showMultiMedia)
|
padding: const EdgeInsets.only(
|
||||||
Padding(
|
right: 8,
|
||||||
padding: const EdgeInsets.only(
|
top: 5,
|
||||||
right: 8,
|
bottom: 5,
|
||||||
top: 5,
|
),
|
||||||
bottom: 5,
|
child: TapDownWrapper(
|
||||||
),
|
onTap: () {
|
||||||
|
if (url.isNotEmpty) {
|
||||||
|
LinkUtil.launch(
|
||||||
|
url,
|
||||||
|
useHackiForHnLink: false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onTap();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: layoutHeight,
|
height: layoutHeight,
|
||||||
width: layoutHeight,
|
width: layoutHeight,
|
||||||
@ -207,10 +218,13 @@ class LinkView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
else
|
)
|
||||||
const SizedBox(width: Dimens.pt5),
|
else
|
||||||
SizedBox(
|
const SizedBox(width: Dimens.pt5),
|
||||||
|
TapDownWrapper(
|
||||||
|
onTap: onTap,
|
||||||
|
child: SizedBox(
|
||||||
height: layoutHeight,
|
height: layoutHeight,
|
||||||
width: layoutWidth - layoutHeight - 8,
|
width: layoutWidth - layoutHeight - 8,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -258,8 +272,8 @@ class LinkView extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -37,37 +37,33 @@ class StoryTile extends StatelessWidget {
|
|||||||
return Semantics(
|
return Semantics(
|
||||||
label: story.screenReaderLabel,
|
label: story.screenReaderLabel,
|
||||||
excludeSemantics: true,
|
excludeSemantics: true,
|
||||||
child: TapDownWrapper(
|
child: Padding(
|
||||||
onTap: onTap,
|
padding: const EdgeInsets.symmetric(
|
||||||
child: Padding(
|
horizontal: Dimens.pt12,
|
||||||
padding: const EdgeInsets.symmetric(
|
),
|
||||||
horizontal: Dimens.pt12,
|
child: LinkPreview(
|
||||||
|
story: story,
|
||||||
|
link: story.url,
|
||||||
|
isOfflineReading:
|
||||||
|
context.read<StoriesBloc>().state.isOfflineReading,
|
||||||
|
placeholderWidget: _LinkPreviewPlaceholder(
|
||||||
|
height: height,
|
||||||
),
|
),
|
||||||
child: AbsorbPointer(
|
errorImage: Constants.hackerNewsLogoLink,
|
||||||
child: LinkPreview(
|
backgroundColor: Palette.transparent,
|
||||||
story: story,
|
borderRadius: Dimens.zero,
|
||||||
link: story.url,
|
removeElevation: true,
|
||||||
isOfflineReading:
|
bodyMaxLines: context.storyTileMaxLines,
|
||||||
context.read<StoriesBloc>().state.isOfflineReading,
|
errorTitle: story.title,
|
||||||
placeholderWidget: _LinkPreviewPlaceholder(
|
titleStyle: TextStyle(
|
||||||
height: height,
|
color: hasRead
|
||||||
),
|
? Palette.grey[500]
|
||||||
errorImage: Constants.hackerNewsLogoLink,
|
: Theme.of(context).textTheme.bodyLarge?.color,
|
||||||
backgroundColor: Palette.transparent,
|
fontWeight: FontWeight.bold,
|
||||||
borderRadius: Dimens.zero,
|
|
||||||
removeElevation: true,
|
|
||||||
bodyMaxLines: context.storyTileMaxLines,
|
|
||||||
errorTitle: story.title,
|
|
||||||
titleStyle: TextStyle(
|
|
||||||
color: hasRead
|
|
||||||
? Palette.grey[500]
|
|
||||||
: Theme.of(context).textTheme.bodyLarge?.color,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
showMetadata: showMetadata,
|
|
||||||
showUrl: showUrl,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
showMetadata: showMetadata,
|
||||||
|
showUrl: showUrl,
|
||||||
|
onTap: onTap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1375,4 +1375,4 @@ packages:
|
|||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.19.0 <3.0.0"
|
dart: ">=2.19.0 <3.0.0"
|
||||||
flutter: ">=3.7.9"
|
flutter: ">=3.7.10"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 1.4.1+105
|
version: 1.4.2+106
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
flutter: "3.7.9"
|
flutter: "3.7.10"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
adaptive_theme: ^3.2.0
|
adaptive_theme: ^3.2.0
|
||||||
|
Submodule submodules/flutter updated: 62bd79521d...4b12645012
Reference in New Issue
Block a user