mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
c2b66d29c3 | |||
e0a53e44b2 | |||
4cf8379db0 | |||
c1c26bf0e0 | |||
29e2f4163d | |||
c3de80015d | |||
436cd9ce8b | |||
efb326be68 | |||
047903fe24 |
4
.github/workflows/commit_check.yml
vendored
4
.github/workflows/commit_check.yml
vendored
@ -12,12 +12,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: "3.7.0"
|
FLUTTER_VERSION: "3.7.1"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: '3.7.0'
|
flutter-version: '3.7.1'
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
- run: flutter pub get
|
- run: flutter pub get
|
||||||
- run: flutter format --set-exit-if-changed .
|
- run: flutter format --set-exit-if-changed .
|
||||||
|
2
.github/workflows/publish_ios.yml
vendored
2
.github/workflows/publish_ios.yml
vendored
@ -31,7 +31,7 @@ jobs:
|
|||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
cache: true
|
cache: true
|
||||||
flutter-version: 3.7.0
|
flutter-version: 3.7.1
|
||||||
- run: flutter pub get
|
- run: flutter pub get
|
||||||
- run: flutter format --set-exit-if-changed .
|
- run: flutter format --set-exit-if-changed .
|
||||||
- run: flutter analyze
|
- run: flutter analyze
|
||||||
|
3
fastlane/metadata/android/en-US/changelogs/84.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/84.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
- Customization of tab bar.
|
||||||
|
- Option to enable swipe gesture for switching between tabs.
|
||||||
|
- Access to action menu from home screen.
|
@ -83,7 +83,9 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
final int pageSize = _getPageSize(isComplexTile: isComplexTile);
|
final int pageSize = _getPageSize(isComplexTile: isComplexTile);
|
||||||
emit(
|
emit(
|
||||||
const StoriesState.init().copyWith(
|
const StoriesState.init().copyWith(
|
||||||
offlineReading: hasCachedStories,
|
offlineReading: hasCachedStories &&
|
||||||
|
// Only go into offline mode in the next session.
|
||||||
|
state.downloadStatus == StoriesDownloadStatus.initial,
|
||||||
currentPageSize: pageSize,
|
currentPageSize: pageSize,
|
||||||
downloadStatus: state.downloadStatus,
|
downloadStatus: state.downloadStatus,
|
||||||
storiesDownloaded: state.storiesDownloaded,
|
storiesDownloaded: state.storiesDownloaded,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
|
|
||||||
abstract class Constants {
|
abstract class Constants {
|
||||||
static const String endUserAgreementLink =
|
static const String endUserAgreementLink =
|
||||||
'https://www.termsfeed.com/live/c1417f5c-a48b-4bd7-93b2-9cd4577bfc45';
|
'https://www.termsfeed.com/live/c1417f5c-a48b-4bd7-93b2-9cd4577bfc45';
|
||||||
@ -34,16 +36,16 @@ abstract class Constants {
|
|||||||
static const String featureLogIn = 'log_in';
|
static const String featureLogIn = 'log_in';
|
||||||
static const String featurePinToTop = 'pin_to_top';
|
static const String featurePinToTop = 'pin_to_top';
|
||||||
|
|
||||||
static const List<String> happyFaces = <String>[
|
static final String happyFace = <String>[
|
||||||
'(๑•̀ㅂ•́)و✧',
|
'(๑•̀ㅂ•́)و✧',
|
||||||
'( ͡• ͜ʖ ͡•)',
|
'( ͡• ͜ʖ ͡•)',
|
||||||
'( ͡~ ͜ʖ ͡°)',
|
'( ͡~ ͜ʖ ͡°)',
|
||||||
'٩(˘◡˘)۶',
|
'٩(˘◡˘)۶',
|
||||||
'(─‿‿─)',
|
'(─‿‿─)',
|
||||||
'(¬‿¬)',
|
'(¬‿¬)',
|
||||||
];
|
].pickRandomly()!;
|
||||||
|
|
||||||
static const List<String> sadFaces = <String>[
|
static final String sadFace = <String>[
|
||||||
'ಥ_ಥ',
|
'ಥ_ಥ',
|
||||||
'(╯°□°)╯︵ ┻━┻',
|
'(╯°□°)╯︵ ┻━┻',
|
||||||
r'¯\_(ツ)_/¯',
|
r'¯\_(ツ)_/¯',
|
||||||
@ -53,7 +55,7 @@ abstract class Constants {
|
|||||||
'(ㆆ_ㆆ)',
|
'(ㆆ_ㆆ)',
|
||||||
'ʕ•́ᴥ•̀ʔっ',
|
'ʕ•́ᴥ•̀ʔっ',
|
||||||
'(ㆆ_ㆆ)',
|
'(ㆆ_ㆆ)',
|
||||||
];
|
].pickRandomly()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class RegExpConstants {
|
abstract class RegExpConstants {
|
||||||
|
@ -56,13 +56,6 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggle(BooleanPreference preference) {
|
|
||||||
final BooleanPreference updatedPreference =
|
|
||||||
preference.copyWith(val: !preference.val) as BooleanPreference;
|
|
||||||
emit(state.copyWithPreference(updatedPreference));
|
|
||||||
_preferenceRepository.setBool(preference.key, !preference.val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void update<T>(Preference<T> preference, {required T to}) {
|
void update<T>(Preference<T> preference, {required T to}) {
|
||||||
final T value = to;
|
final T value = to;
|
||||||
final Preference<T> updatedPreference = preference.copyWith(val: value);
|
final Preference<T> updatedPreference = preference.copyWith(val: value);
|
||||||
|
@ -119,11 +119,45 @@ extension StateExtension on State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onShareTapped(Item item, Rect? rect) {
|
Future<void> onShareTapped(Item item, Rect? rect) async {
|
||||||
Share.share(
|
late final String? linkToShare;
|
||||||
'https://news.ycombinator.com/item?id=${item.id}',
|
if (item.url.isNotEmpty) {
|
||||||
sharePositionOrigin: rect,
|
linkToShare = await showModalBottomSheet<String>(
|
||||||
);
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: 140,
|
||||||
|
color: Theme.of(context).canvasColor,
|
||||||
|
child: Material(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
onTap: () => Navigator.pop(context, item.url),
|
||||||
|
title: const Text('Link to article'),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
onTap: () => Navigator.pop(
|
||||||
|
context,
|
||||||
|
'https://news.ycombinator.com/item?id=${item.id}',
|
||||||
|
),
|
||||||
|
title: const Text('Link to HN'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
linkToShare = 'https://news.ycombinator.com/item?id=${item.id}';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkToShare != null) {
|
||||||
|
await Share.share(
|
||||||
|
linkToShare,
|
||||||
|
sharePositionOrigin: rect,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFlagTapped(Item item) {
|
void onFlagTapped(Item item) {
|
||||||
|
@ -20,23 +20,7 @@ class Comment extends Item {
|
|||||||
type: '',
|
type: '',
|
||||||
);
|
);
|
||||||
|
|
||||||
Comment.fromJson(Map<String, dynamic> json, {this.level = 0})
|
Comment.fromJson(super.json, {this.level = 0}) : super.fromJson();
|
||||||
: super(
|
|
||||||
id: json['id'] as int? ?? 0,
|
|
||||||
time: json['time'] as int? ?? 0,
|
|
||||||
by: json['by'] as String? ?? '',
|
|
||||||
text: json['text'] as String? ?? '',
|
|
||||||
kids: (json['kids'] as List<dynamic>?)?.cast<int>() ?? <int>[],
|
|
||||||
parent: json['parent'] as int? ?? 0,
|
|
||||||
deleted: json['deleted'] as bool? ?? false,
|
|
||||||
score: json['score'] as int? ?? 0,
|
|
||||||
descendants: 0,
|
|
||||||
dead: json['dead'] as bool? ?? false,
|
|
||||||
parts: <int>[],
|
|
||||||
title: '',
|
|
||||||
url: '',
|
|
||||||
type: '',
|
|
||||||
);
|
|
||||||
|
|
||||||
final int level;
|
final int level;
|
||||||
|
|
||||||
|
@ -44,11 +44,11 @@ class Item extends Equatable {
|
|||||||
title = json['title'] as String? ?? '',
|
title = json['title'] as String? ?? '',
|
||||||
text = json['text'] as String? ?? '',
|
text = json['text'] as String? ?? '',
|
||||||
url = json['url'] as String? ?? '',
|
url = json['url'] as String? ?? '',
|
||||||
kids = <int>[],
|
kids = (json['kids'] as List<dynamic>?)?.cast<int>() ?? <int>[],
|
||||||
dead = json['dead'] as bool? ?? false,
|
dead = json['dead'] as bool? ?? false,
|
||||||
deleted = json['deleted'] as bool? ?? false,
|
deleted = json['deleted'] as bool? ?? false,
|
||||||
parent = json['parent'] as int? ?? 0,
|
parent = json['parent'] as int? ?? 0,
|
||||||
parts = <int>[],
|
parts = (json['parts'] as List<dynamic>?)?.cast<int>() ?? <int>[],
|
||||||
type = json['type'] as String? ?? '';
|
type = json['type'] as String? ?? '';
|
||||||
|
|
||||||
final int id;
|
final int id;
|
||||||
|
@ -9,4 +9,5 @@ export 'post_data.dart';
|
|||||||
export 'preference.dart';
|
export 'preference.dart';
|
||||||
export 'search_params.dart';
|
export 'search_params.dart';
|
||||||
export 'story.dart';
|
export 'story.dart';
|
||||||
|
export 'story_type.dart';
|
||||||
export 'user.dart';
|
export 'user.dart';
|
||||||
|
@ -24,41 +24,11 @@ class PollOption extends Item {
|
|||||||
|
|
||||||
PollOption.empty()
|
PollOption.empty()
|
||||||
: ratio = 0,
|
: ratio = 0,
|
||||||
super(
|
super.empty();
|
||||||
id: 0,
|
|
||||||
score: 0,
|
|
||||||
descendants: 0,
|
|
||||||
time: 0,
|
|
||||||
by: '',
|
|
||||||
title: '',
|
|
||||||
url: '',
|
|
||||||
kids: <int>[],
|
|
||||||
dead: false,
|
|
||||||
parts: <int>[],
|
|
||||||
deleted: false,
|
|
||||||
parent: 0,
|
|
||||||
text: '',
|
|
||||||
type: '',
|
|
||||||
);
|
|
||||||
|
|
||||||
PollOption.fromJson(Map<String, dynamic> json)
|
PollOption.fromJson(super.json)
|
||||||
: ratio = 0,
|
: ratio = 0,
|
||||||
super(
|
super.fromJson();
|
||||||
descendants: 0,
|
|
||||||
id: json['id'] as int? ?? 0,
|
|
||||||
score: json['score'] as int? ?? 0,
|
|
||||||
time: json['time'] as int? ?? 0,
|
|
||||||
by: json['by'] as String? ?? '',
|
|
||||||
title: json['title'] as String? ?? '',
|
|
||||||
url: json['url'] as String? ?? '',
|
|
||||||
kids: <int>[],
|
|
||||||
text: json['text'] as String? ?? '',
|
|
||||||
dead: json['dead'] as bool? ?? false,
|
|
||||||
deleted: json['deleted'] as bool? ?? false,
|
|
||||||
type: json['type'] as String? ?? '',
|
|
||||||
parts: <int>[],
|
|
||||||
parent: 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
final double ratio;
|
final double ratio;
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:collection';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
@ -13,26 +14,29 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
|||||||
|
|
||||||
Preference<T> copyWith({required T? val});
|
Preference<T> copyWith({required T? val});
|
||||||
|
|
||||||
static List<Preference<dynamic>> allPreferences = <Preference<dynamic>>[
|
static final List<Preference<dynamic>> allPreferences =
|
||||||
// Order of these first three preferences does not matter.
|
UnmodifiableListView<Preference<dynamic>>(
|
||||||
FetchModePreference(),
|
<Preference<dynamic>>[
|
||||||
CommentsOrderPreference(),
|
// Order of these first four preferences does not matter.
|
||||||
FontSizePreference(),
|
FetchModePreference(),
|
||||||
TabOrderPreference(),
|
CommentsOrderPreference(),
|
||||||
// Order of items below matters and
|
FontSizePreference(),
|
||||||
// reflects the order on settings screen.
|
TabOrderPreference(),
|
||||||
const DisplayModePreference(),
|
// Order of items below matters and
|
||||||
const MetadataModePreference(),
|
// reflects the order on settings screen.
|
||||||
const StoryUrlModePreference(),
|
const DisplayModePreference(),
|
||||||
const NotificationModePreference(),
|
const MetadataModePreference(),
|
||||||
const SwipeGesturePreference(),
|
const StoryUrlModePreference(),
|
||||||
const CollapseModePreference(),
|
const NotificationModePreference(),
|
||||||
NavigationModePreference(),
|
const SwipeGesturePreference(),
|
||||||
const ReaderModePreference(),
|
const CollapseModePreference(),
|
||||||
const MarkReadStoriesModePreference(),
|
NavigationModePreference(),
|
||||||
const EyeCandyModePreference(),
|
const ReaderModePreference(),
|
||||||
const TrueDarkModePreference(),
|
const MarkReadStoriesModePreference(),
|
||||||
];
|
const EyeCandyModePreference(),
|
||||||
|
const TrueDarkModePreference(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[key];
|
List<Object?> get props => <Object?>[key];
|
||||||
@ -81,7 +85,7 @@ class SwipeGesturePreference extends BooleanPreference {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get subtitle =>
|
String get subtitle =>
|
||||||
'''Enable swipe gesture for switching between tabs. If enabled, long press on Story tile to trigger the action menu.''';
|
'''enable swipe gesture for switching between tabs. If enabled, long press on Story tile to trigger the action menu.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationModePreference extends BooleanPreference {
|
class NotificationModePreference extends BooleanPreference {
|
||||||
@ -118,6 +122,10 @@ class CollapseModePreference extends BooleanPreference {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => 'Tap Anywhere to Collapse';
|
String get title => 'Tap Anywhere to Collapse';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get subtitle =>
|
||||||
|
'''if disabled, tap on the top of comment tile to collapse.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The value deciding whether or not the story
|
/// The value deciding whether or not the story
|
||||||
|
@ -1,41 +1,6 @@
|
|||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/models/item.dart';
|
import 'package:hacki/models/item.dart';
|
||||||
|
|
||||||
enum StoryType {
|
|
||||||
top('topstories'),
|
|
||||||
best('beststories'),
|
|
||||||
latest('newstories'),
|
|
||||||
ask('askstories'),
|
|
||||||
show('showstories');
|
|
||||||
|
|
||||||
const StoryType(this.path);
|
|
||||||
|
|
||||||
final String path;
|
|
||||||
|
|
||||||
String get label {
|
|
||||||
switch (this) {
|
|
||||||
case StoryType.top:
|
|
||||||
return 'TOP';
|
|
||||||
case StoryType.best:
|
|
||||||
return 'BEST';
|
|
||||||
case StoryType.latest:
|
|
||||||
return 'NEW';
|
|
||||||
case StoryType.ask:
|
|
||||||
return 'ASK';
|
|
||||||
case StoryType.show:
|
|
||||||
return 'SHOW';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int convertToSettingsValue(List<StoryType> tabs) {
|
|
||||||
return int.parse(
|
|
||||||
tabs
|
|
||||||
.map((StoryType e) => e.index.toString())
|
|
||||||
.reduce((String value, String element) => '$value$element'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Story extends Item {
|
class Story extends Item {
|
||||||
const Story({
|
const Story({
|
||||||
required super.descendants,
|
required super.descendants,
|
||||||
@ -55,23 +20,7 @@ class Story extends Item {
|
|||||||
parent: 0,
|
parent: 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
Story.empty()
|
Story.empty() : super.empty();
|
||||||
: super(
|
|
||||||
id: 0,
|
|
||||||
score: 0,
|
|
||||||
descendants: 0,
|
|
||||||
time: 0,
|
|
||||||
by: '',
|
|
||||||
title: '',
|
|
||||||
url: '',
|
|
||||||
kids: <int>[],
|
|
||||||
dead: false,
|
|
||||||
parts: <int>[],
|
|
||||||
deleted: false,
|
|
||||||
parent: 0,
|
|
||||||
text: '',
|
|
||||||
type: '',
|
|
||||||
);
|
|
||||||
|
|
||||||
Story.placeholder()
|
Story.placeholder()
|
||||||
: super(
|
: super(
|
||||||
@ -91,23 +40,7 @@ class Story extends Item {
|
|||||||
type: '',
|
type: '',
|
||||||
);
|
);
|
||||||
|
|
||||||
Story.fromJson(Map<String, dynamic> json)
|
Story.fromJson(super.json) : super.fromJson();
|
||||||
: super(
|
|
||||||
descendants: json['descendants'] as int? ?? 0,
|
|
||||||
id: json['id'] as int? ?? 0,
|
|
||||||
score: json['score'] as int? ?? 0,
|
|
||||||
time: json['time'] as int? ?? 0,
|
|
||||||
by: json['by'] as String? ?? '',
|
|
||||||
title: json['title'] as String? ?? '',
|
|
||||||
url: json['url'] as String? ?? '',
|
|
||||||
kids: (json['kids'] as List<dynamic>?)?.cast<int>() ?? <int>[],
|
|
||||||
text: json['text'] as String? ?? '',
|
|
||||||
dead: json['dead'] as bool? ?? false,
|
|
||||||
deleted: json['deleted'] as bool? ?? false,
|
|
||||||
type: json['type'] as String? ?? '',
|
|
||||||
parts: (json['parts'] as List<dynamic>?)?.cast<int>() ?? <int>[],
|
|
||||||
parent: 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
String get metadata =>
|
String get metadata =>
|
||||||
'''$score point${score > 1 ? 's' : ''} by $by $postedDate | $descendants comment${descendants > 1 ? 's' : ''}''';
|
'''$score point${score > 1 ? 's' : ''} by $by $postedDate | $descendants comment${descendants > 1 ? 's' : ''}''';
|
||||||
|
34
lib/models/story_type.dart
Normal file
34
lib/models/story_type.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
enum StoryType {
|
||||||
|
top('topstories'),
|
||||||
|
best('beststories'),
|
||||||
|
latest('newstories'),
|
||||||
|
ask('askstories'),
|
||||||
|
show('showstories');
|
||||||
|
|
||||||
|
const StoryType(this.path);
|
||||||
|
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
String get label {
|
||||||
|
switch (this) {
|
||||||
|
case StoryType.top:
|
||||||
|
return 'TOP';
|
||||||
|
case StoryType.best:
|
||||||
|
return 'BEST';
|
||||||
|
case StoryType.latest:
|
||||||
|
return 'NEW';
|
||||||
|
case StoryType.ask:
|
||||||
|
return 'ASK';
|
||||||
|
case StoryType.show:
|
||||||
|
return 'SHOW';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int convertToSettingsValue(List<StoryType> tabs) {
|
||||||
|
return int.parse(
|
||||||
|
tabs
|
||||||
|
.map((StoryType e) => e.index.toString())
|
||||||
|
.reduce((String value, String element) => '$value$element'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,8 @@ import 'dart:io';
|
|||||||
import 'package:feature_discovery/feature_discovery.dart';
|
import 'package:feature_discovery/feature_discovery.dart';
|
||||||
import 'package:flutter/material.dart' hide Badge;
|
import 'package:flutter/material.dart' hide Badge;
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_fadein/flutter_fadein.dart';
|
|
||||||
import 'package:flutter_siri_suggestions/flutter_siri_suggestions.dart';
|
import 'package:flutter_siri_suggestions/flutter_siri_suggestions.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
|
||||||
import 'package:hacki/blocs/blocs.dart';
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
@ -18,6 +15,7 @@ import 'package:hacki/extensions/extensions.dart';
|
|||||||
import 'package:hacki/main.dart';
|
import 'package:hacki/main.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
|
import 'package:hacki/screens/home/widgets/widgets.dart';
|
||||||
import 'package:hacki/screens/screens.dart';
|
import 'package:hacki/screens/screens.dart';
|
||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
import 'package:hacki/services/services.dart';
|
import 'package:hacki/services/services.dart';
|
||||||
@ -145,57 +143,6 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
previous.metadataEnabled != current.metadataEnabled ||
|
previous.metadataEnabled != current.metadataEnabled ||
|
||||||
previous.swipeGestureEnabled != current.swipeGestureEnabled,
|
previous.swipeGestureEnabled != current.swipeGestureEnabled,
|
||||||
builder: (BuildContext context, PreferenceState preferenceState) {
|
builder: (BuildContext context, PreferenceState preferenceState) {
|
||||||
final BlocBuilder<PinCubit, PinState> pinnedStories =
|
|
||||||
BlocBuilder<PinCubit, PinState>(
|
|
||||||
builder: (BuildContext context, PinState state) {
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
for (final Story story in state.pinnedStories)
|
|
||||||
FadeIn(
|
|
||||||
child: Slidable(
|
|
||||||
startActionPane: ActionPane(
|
|
||||||
motion: const BehindMotion(),
|
|
||||||
children: <Widget>[
|
|
||||||
SlidableAction(
|
|
||||||
onPressed: (_) {
|
|
||||||
HapticFeedback.lightImpact();
|
|
||||||
context.read<PinCubit>().unpinStory(story);
|
|
||||||
},
|
|
||||||
backgroundColor: Palette.red,
|
|
||||||
foregroundColor: Palette.white,
|
|
||||||
icon: preferenceState.complexStoryTileEnabled
|
|
||||||
? Icons.close
|
|
||||||
: null,
|
|
||||||
label: 'Unpin',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: ColoredBox(
|
|
||||||
color: Palette.orangeAccent.withOpacity(0.2),
|
|
||||||
child: StoryTile(
|
|
||||||
key: ValueKey<String>('${story.id}-PinnedStoryTile'),
|
|
||||||
story: story,
|
|
||||||
onTap: () => onStoryTapped(story, isPin: true),
|
|
||||||
showWebPreview:
|
|
||||||
preferenceState.complexStoryTileEnabled,
|
|
||||||
showMetadata: preferenceState.metadataEnabled,
|
|
||||||
showUrl: preferenceState.urlEnabled,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (state.pinnedStories.isNotEmpty)
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: Dimens.pt12),
|
|
||||||
child: Divider(
|
|
||||||
color: Palette.orangeAccent,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: tabLength,
|
length: tabLength,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
@ -235,7 +182,10 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
StoriesListView(
|
StoriesListView(
|
||||||
key: ValueKey<StoryType>(type),
|
key: ValueKey<StoryType>(type),
|
||||||
storyType: type,
|
storyType: type,
|
||||||
header: pinnedStories,
|
header: PinnedStories(
|
||||||
|
preferenceState: preferenceState,
|
||||||
|
onStoryTapped: onStoryTapped,
|
||||||
|
),
|
||||||
onStoryTapped: onStoryTapped,
|
onStoryTapped: onStoryTapped,
|
||||||
),
|
),
|
||||||
const ProfileScreen(),
|
const ProfileScreen(),
|
||||||
@ -251,11 +201,11 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
return ScreenTypeLayout.builder(
|
return ScreenTypeLayout.builder(
|
||||||
mobile: (BuildContext context) {
|
mobile: (BuildContext context) {
|
||||||
context.read<SplitViewCubit>().disableSplitView();
|
context.read<SplitViewCubit>().disableSplitView();
|
||||||
return _MobileHomeScreen(
|
return MobileHomeScreen(
|
||||||
homeScreen: homeScreen,
|
homeScreen: homeScreen,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
tablet: (BuildContext context) => _TabletHomeScreen(
|
tablet: (BuildContext context) => TabletHomeScreen(
|
||||||
homeScreen: homeScreen,
|
homeScreen: homeScreen,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -385,112 +335,3 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MobileHomeScreen extends StatelessWidget {
|
|
||||||
const _MobileHomeScreen({
|
|
||||||
required this.homeScreen,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget homeScreen;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
Positioned.fill(child: homeScreen),
|
|
||||||
if (!context.read<ReminderCubit>().state.hasShown)
|
|
||||||
const Positioned(
|
|
||||||
left: Dimens.pt24,
|
|
||||||
right: Dimens.pt24,
|
|
||||||
bottom: Dimens.pt36,
|
|
||||||
height: Dimens.pt40,
|
|
||||||
child: CountdownReminder(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TabletHomeScreen extends StatelessWidget {
|
|
||||||
const _TabletHomeScreen({
|
|
||||||
required this.homeScreen,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget homeScreen;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ResponsiveBuilder(
|
|
||||||
builder: (BuildContext context, SizingInformation sizeInfo) {
|
|
||||||
context.read<SplitViewCubit>().enableSplitView();
|
|
||||||
double homeScreenWidth = 428;
|
|
||||||
|
|
||||||
if (sizeInfo.screenSize.width < homeScreenWidth * 2) {
|
|
||||||
homeScreenWidth = 345;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BlocBuilder<SplitViewCubit, SplitViewState>(
|
|
||||||
buildWhen: (SplitViewState previous, SplitViewState current) =>
|
|
||||||
previous.expanded != current.expanded,
|
|
||||||
builder: (BuildContext context, SplitViewState state) {
|
|
||||||
return Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
AnimatedPositioned(
|
|
||||||
left: Dimens.zero,
|
|
||||||
top: Dimens.zero,
|
|
||||||
bottom: Dimens.zero,
|
|
||||||
width: homeScreenWidth,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.elasticOut,
|
|
||||||
child: homeScreen,
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
left: Dimens.pt24,
|
|
||||||
bottom: Dimens.pt36,
|
|
||||||
height: Dimens.pt40,
|
|
||||||
width: homeScreenWidth - Dimens.pt24,
|
|
||||||
child: const CountdownReminder(),
|
|
||||||
),
|
|
||||||
AnimatedPositioned(
|
|
||||||
right: Dimens.zero,
|
|
||||||
top: Dimens.zero,
|
|
||||||
bottom: Dimens.zero,
|
|
||||||
left: state.expanded ? Dimens.zero : homeScreenWidth,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.elasticOut,
|
|
||||||
child: const _TabletStoryView(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TabletStoryView extends StatelessWidget {
|
|
||||||
const _TabletStoryView();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<SplitViewCubit, SplitViewState>(
|
|
||||||
buildWhen: (SplitViewState previous, SplitViewState current) =>
|
|
||||||
previous.itemScreenArgs != current.itemScreenArgs,
|
|
||||||
builder: (BuildContext context, SplitViewState state) {
|
|
||||||
if (state.itemScreenArgs != null) {
|
|
||||||
return ItemScreen.build(context, state.itemScreenArgs!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Material(
|
|
||||||
child: ColoredBox(
|
|
||||||
color: Theme.of(context).canvasColor,
|
|
||||||
child: const Center(
|
|
||||||
child: Text('Tap on story tile to view comments.'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
31
lib/screens/home/widgets/mobile_home_screen.dart
Normal file
31
lib/screens/home/widgets/mobile_home_screen.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:flutter/material.dart' hide Badge;
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
|
||||||
|
class MobileHomeScreen extends StatelessWidget {
|
||||||
|
const MobileHomeScreen({
|
||||||
|
super.key,
|
||||||
|
required this.homeScreen,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget homeScreen;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Positioned.fill(child: homeScreen),
|
||||||
|
if (!context.read<ReminderCubit>().state.hasShown)
|
||||||
|
const Positioned(
|
||||||
|
left: Dimens.pt24,
|
||||||
|
right: Dimens.pt24,
|
||||||
|
bottom: Dimens.pt36,
|
||||||
|
height: Dimens.pt40,
|
||||||
|
child: CountdownReminder(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
72
lib/screens/home/widgets/pinned_stories.dart
Normal file
72
lib/screens/home/widgets/pinned_stories.dart
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import 'package:flutter/material.dart' hide Badge;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_fadein/flutter_fadein.dart';
|
||||||
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
|
import 'package:hacki/models/models.dart';
|
||||||
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
|
||||||
|
class PinnedStories extends StatelessWidget {
|
||||||
|
const PinnedStories({
|
||||||
|
super.key,
|
||||||
|
required this.preferenceState,
|
||||||
|
required this.onStoryTapped,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PreferenceState preferenceState;
|
||||||
|
final void Function(Story story, {bool isPin}) onStoryTapped;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<PinCubit, PinState>(
|
||||||
|
builder: (BuildContext context, PinState state) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
for (final Story story in state.pinnedStories)
|
||||||
|
FadeIn(
|
||||||
|
child: Slidable(
|
||||||
|
startActionPane: ActionPane(
|
||||||
|
motion: const BehindMotion(),
|
||||||
|
children: <Widget>[
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (_) {
|
||||||
|
HapticFeedback.lightImpact();
|
||||||
|
context.read<PinCubit>().unpinStory(story);
|
||||||
|
},
|
||||||
|
backgroundColor: Palette.red,
|
||||||
|
foregroundColor: Palette.white,
|
||||||
|
icon: preferenceState.complexStoryTileEnabled
|
||||||
|
? Icons.close
|
||||||
|
: null,
|
||||||
|
label: 'Unpin',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Palette.orangeAccent.withOpacity(0.2),
|
||||||
|
child: StoryTile(
|
||||||
|
key: ValueKey<String>('${story.id}-PinnedStoryTile'),
|
||||||
|
story: story,
|
||||||
|
onTap: () => onStoryTapped(story, isPin: true),
|
||||||
|
showWebPreview: preferenceState.complexStoryTileEnabled,
|
||||||
|
showMetadata: preferenceState.metadataEnabled,
|
||||||
|
showUrl: preferenceState.urlEnabled,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.pinnedStories.isNotEmpty)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: Dimens.pt12),
|
||||||
|
child: Divider(
|
||||||
|
color: Palette.orangeAccent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
92
lib/screens/home/widgets/tablet_home_screen.dart
Normal file
92
lib/screens/home/widgets/tablet_home_screen.dart
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import 'package:flutter/material.dart' hide Badge;
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
|
import 'package:hacki/screens/screens.dart';
|
||||||
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
class TabletHomeScreen extends StatelessWidget {
|
||||||
|
const TabletHomeScreen({
|
||||||
|
super.key,
|
||||||
|
required this.homeScreen,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget homeScreen;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (BuildContext context, SizingInformation sizeInfo) {
|
||||||
|
context.read<SplitViewCubit>().enableSplitView();
|
||||||
|
double homeScreenWidth = 428;
|
||||||
|
|
||||||
|
if (sizeInfo.screenSize.width < homeScreenWidth * 2) {
|
||||||
|
homeScreenWidth = 345;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlocBuilder<SplitViewCubit, SplitViewState>(
|
||||||
|
buildWhen: (SplitViewState previous, SplitViewState current) =>
|
||||||
|
previous.expanded != current.expanded,
|
||||||
|
builder: (BuildContext context, SplitViewState state) {
|
||||||
|
return Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
AnimatedPositioned(
|
||||||
|
left: Dimens.zero,
|
||||||
|
top: Dimens.zero,
|
||||||
|
bottom: Dimens.zero,
|
||||||
|
width: homeScreenWidth,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
child: homeScreen,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: Dimens.pt24,
|
||||||
|
bottom: Dimens.pt36,
|
||||||
|
height: Dimens.pt40,
|
||||||
|
width: homeScreenWidth - Dimens.pt24,
|
||||||
|
child: const CountdownReminder(),
|
||||||
|
),
|
||||||
|
AnimatedPositioned(
|
||||||
|
right: Dimens.zero,
|
||||||
|
top: Dimens.zero,
|
||||||
|
bottom: Dimens.zero,
|
||||||
|
left: state.expanded ? Dimens.zero : homeScreenWidth,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
child: const _TabletStoryView(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TabletStoryView extends StatelessWidget {
|
||||||
|
const _TabletStoryView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<SplitViewCubit, SplitViewState>(
|
||||||
|
buildWhen: (SplitViewState previous, SplitViewState current) =>
|
||||||
|
previous.itemScreenArgs != current.itemScreenArgs,
|
||||||
|
builder: (BuildContext context, SplitViewState state) {
|
||||||
|
if (state.itemScreenArgs != null) {
|
||||||
|
return ItemScreen.build(context, state.itemScreenArgs!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Theme.of(context).canvasColor,
|
||||||
|
child: const Center(
|
||||||
|
child: Text('Tap on story tile to view comments.'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
3
lib/screens/home/widgets/widgets.dart
Normal file
3
lib/screens/home/widgets/widgets.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export 'mobile_home_screen.dart';
|
||||||
|
export 'pinned_stories.dart';
|
||||||
|
export 'tablet_home_screen.dart';
|
@ -149,7 +149,6 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
|
|||||||
initialRefreshStatus: RefreshStatus.refreshing,
|
initialRefreshStatus: RefreshStatus.refreshing,
|
||||||
);
|
);
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
final String happyFace = Constants.happyFaces.pickRandomly()!;
|
|
||||||
final Throttle storyLinkTapThrottle = Throttle(
|
final Throttle storyLinkTapThrottle = Throttle(
|
||||||
delay: _storyLinkTapThrottleDelay,
|
delay: _storyLinkTapThrottleDelay,
|
||||||
);
|
);
|
||||||
@ -233,8 +232,7 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
|
|||||||
context.read<EditCubit>().state.replyingTo == null
|
context.read<EditCubit>().state.replyingTo == null
|
||||||
? 'updated'
|
? 'updated'
|
||||||
: 'submitted';
|
: 'submitted';
|
||||||
final String msg =
|
final String msg = 'Comment $verb! ${Constants.happyFace}';
|
||||||
'Comment $verb! ${Constants.happyFaces.pickRandomly()}';
|
|
||||||
focusNode.unfocus();
|
focusNode.unfocus();
|
||||||
HapticFeedback.lightImpact();
|
HapticFeedback.lightImpact();
|
||||||
showSnackBar(content: msg);
|
showSnackBar(content: msg);
|
||||||
@ -243,7 +241,7 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
|
|||||||
} else if (postState.status == PostStatus.failure) {
|
} else if (postState.status == PostStatus.failure) {
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
content: 'Something went wrong...'
|
content: 'Something went wrong...'
|
||||||
'${Constants.sadFaces.pickRandomly()}',
|
'${Constants.sadFace}',
|
||||||
label: 'Okay',
|
label: 'Okay',
|
||||||
action: ScaffoldMessenger.of(context).hideCurrentSnackBar,
|
action: ScaffoldMessenger.of(context).hideCurrentSnackBar,
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:hacki/blocs/blocs.dart';
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/extensions/extensions.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';
|
||||||
@ -28,9 +27,10 @@ class LoginDialog extends StatelessWidget {
|
|||||||
return BlocConsumer<AuthBloc, AuthState>(
|
return BlocConsumer<AuthBloc, AuthState>(
|
||||||
listener: (BuildContext context, AuthState state) {
|
listener: (BuildContext context, AuthState state) {
|
||||||
if (state.isLoggedIn) {
|
if (state.isLoggedIn) {
|
||||||
final String happyFace = Constants.happyFaces.pickRandomly()!;
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
showSnackBar(content: 'Logged in successfully! $happyFace');
|
showSnackBar(
|
||||||
|
content: 'Logged in successfully! ${Constants.happyFace}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (BuildContext context, AuthState state) {
|
builder: (BuildContext context, AuthState state) {
|
||||||
|
@ -135,7 +135,7 @@ class MainView extends StatelessWidget {
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: _trailingBoxHeight,
|
height: _trailingBoxHeight,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(Constants.happyFaces.pickRandomly()!),
|
child: Text(Constants.happyFace),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,6 +131,7 @@ class _SettingsState extends State<Settings> {
|
|||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (FetchMode? fetchMode) {
|
onChanged: (FetchMode? fetchMode) {
|
||||||
if (fetchMode != null) {
|
if (fetchMode != null) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
context.read<PreferenceCubit>().update(
|
context.read<PreferenceCubit>().update(
|
||||||
FetchModePreference(),
|
FetchModePreference(),
|
||||||
to: fetchMode.index,
|
to: fetchMode.index,
|
||||||
@ -164,6 +165,7 @@ class _SettingsState extends State<Settings> {
|
|||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (CommentsOrder? order) {
|
onChanged: (CommentsOrder? order) {
|
||||||
if (order != null) {
|
if (order != null) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
context.read<PreferenceCubit>().update(
|
context.read<PreferenceCubit>().update(
|
||||||
CommentsOrderPreference(),
|
CommentsOrderPreference(),
|
||||||
to: order.index,
|
to: order.index,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export 'home_screen.dart';
|
export 'home/home_screen.dart';
|
||||||
export 'item/item_screen.dart';
|
export 'item/item_screen.dart';
|
||||||
export 'profile/profile_screen.dart';
|
export 'profile/profile_screen.dart';
|
||||||
export 'search/search_screen.dart';
|
export 'search/search_screen.dart';
|
||||||
|
@ -4,7 +4,6 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/extensions/extensions.dart';
|
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
import 'package:hacki/utils/html_util.dart';
|
import 'package:hacki/utils/html_util.dart';
|
||||||
@ -43,7 +42,6 @@ abstract class Fetcher {
|
|||||||
final SembastRepository sembastRepository = SembastRepository();
|
final SembastRepository sembastRepository = SembastRepository();
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
final String happyFace = Constants.happyFaces.pickRandomly()!;
|
|
||||||
final String? username = await authRepository.username;
|
final String? username = await authRepository.username;
|
||||||
final List<int> unreadIds = await preferenceRepository.unreadCommentsIds;
|
final List<int> unreadIds = await preferenceRepository.unreadCommentsIds;
|
||||||
|
|
||||||
@ -123,7 +121,7 @@ abstract class Fetcher {
|
|||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
newReply?.id ?? 0,
|
newReply?.id ?? 0,
|
||||||
'You have a new reply! $happyFace',
|
'You have a new reply! ${Constants.happyFace}',
|
||||||
'${newReply?.by}: $text',
|
'${newReply?.by}: $text',
|
||||||
const NotificationDetails(
|
const NotificationDetails(
|
||||||
iOS: DarwinNotificationDetails(
|
iOS: DarwinNotificationDetails(
|
||||||
|
@ -2,14 +2,12 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/extensions/extensions.dart';
|
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
|
|
||||||
class LocalNotification {
|
class LocalNotification {
|
||||||
Future<void> pushForNewReply(Comment newReply, int storyId) async {
|
Future<void> pushForNewReply(Comment newReply, int storyId) async {
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
final String happyFace = Constants.happyFaces.pickRandomly()!;
|
|
||||||
|
|
||||||
final Map<String, int> payloadJson = <String, int>{
|
final Map<String, int> payloadJson = <String, int>{
|
||||||
'commentId': newReply.id,
|
'commentId': newReply.id,
|
||||||
@ -19,7 +17,7 @@ class LocalNotification {
|
|||||||
|
|
||||||
return flutterLocalNotificationsPlugin.show(
|
return flutterLocalNotificationsPlugin.show(
|
||||||
newReply.id,
|
newReply.id,
|
||||||
'You have a new reply! $happyFace',
|
'You have a new reply! ${Constants.happyFace}',
|
||||||
'${newReply.by}: ${newReply.text}',
|
'${newReply.by}: ${newReply.text}',
|
||||||
const NotificationDetails(
|
const NotificationDetails(
|
||||||
iOS: DarwinNotificationDetails(
|
iOS: DarwinNotificationDetails(
|
||||||
|
@ -1358,5 +1358,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.18.0 <4.0.0"
|
dart: ">=2.18.0 <3.0.0"
|
||||||
flutter: ">=3.7.0"
|
flutter: ">=3.7.1"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 1.0.4+82
|
version: 1.0.7+85
|
||||||
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.0"
|
flutter: "3.7.1"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
adaptive_theme: ^3.0.0
|
adaptive_theme: ^3.0.0
|
||||||
|
Submodule submodules/flutter updated: b06b8b2710...7048ed95a5
Reference in New Issue
Block a user