mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
d4fe042245 | |||
b82c4a1777 | |||
7e0d1f0f1d | |||
f405a10c2e | |||
edbad79cd3 | |||
c9d8b2950a |
@ -50,7 +50,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.jiaqifeng.hacki"
|
||||
minSdkVersion 26
|
||||
minSdkVersion 25
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
@ -13,6 +13,9 @@
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
|
2
fastlane/metadata/android/en-US/changelogs/125.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/125.txt
Normal file
@ -0,0 +1,2 @@
|
||||
- Ability to customize text scale factor.
|
||||
- Ability to customize app's accent color.
|
@ -137,11 +137,11 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/workmanager/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||
flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
|
||||
flutter_inappwebview: 3d32228f1304635e7c028b0d4252937730bbc6cf
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
flutter_siri_suggestions: 226fb7ef33d25d3fe0d4aa2a8bcf4b72730c466f
|
||||
@ -155,7 +155,7 @@ SPEC CHECKSUMS:
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
|
||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
synced_shared_preferences: f722742b06d65c7315b8e9f56b794c9fbd5597f7
|
||||
|
@ -1050,4 +1050,4 @@
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
}
|
||||
|
@ -228,16 +228,18 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||
StoryLoaded event,
|
||||
Emitter<StoriesState> emit,
|
||||
) async {
|
||||
final bool hasRead = await _preferenceRepository.hasRead(event.story.id);
|
||||
final bool hidden = _filterCubit.state.keywords.any(
|
||||
(String keyword) =>
|
||||
event.story.title.toLowerCase().contains(keyword) ||
|
||||
event.story.text.toLowerCase().contains(keyword),
|
||||
);
|
||||
final Story story = event.story;
|
||||
final bool hasRead = await _preferenceRepository.hasRead(story.id);
|
||||
final bool hidden = _filterCubit.state.keywords.any((String keyword) {
|
||||
// Match word only.
|
||||
final RegExp regExp = RegExp('\\b($keyword)\\b');
|
||||
return regExp.hasMatch(story.title.toLowerCase()) ||
|
||||
regExp.hasMatch(story.text.toLowerCase());
|
||||
});
|
||||
emit(
|
||||
state.copyWithStoryAdded(
|
||||
type: event.type,
|
||||
story: event.story.copyWith(hidden: hidden),
|
||||
story: story.copyWith(hidden: hidden),
|
||||
hasRead: hasRead,
|
||||
),
|
||||
);
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
|
||||
import 'package:hacki/config/locator.dart';
|
||||
import 'package:hacki/extensions/extensions.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
@ -41,6 +43,16 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
for (final DoublePreference p
|
||||
in Preference.allPreferences.whereType<DoublePreference>()) {
|
||||
initPreference<double>(p).then<double?>((double? value) {
|
||||
final Preference<dynamic> updatedPreference = p.copyWith(val: value);
|
||||
emit(state.copyWithPreference(updatedPreference));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<T?> initPreference<T>(Preference<T> preference) async {
|
||||
@ -48,6 +60,10 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
||||
case int:
|
||||
final int? value = await _preferenceRepository.getInt(preference.key);
|
||||
return value as T?;
|
||||
case double:
|
||||
final double? value =
|
||||
await _preferenceRepository.getDouble(preference.key);
|
||||
return value as T?;
|
||||
case bool:
|
||||
final bool? value = await _preferenceRepository.getBool(preference.key);
|
||||
return value as T?;
|
||||
@ -56,19 +72,27 @@ class PreferenceCubit extends Cubit<PreferenceState> {
|
||||
}
|
||||
}
|
||||
|
||||
void update<T>(Preference<T> preference, {required T to}) {
|
||||
final T value = to;
|
||||
final Preference<T> updatedPreference = preference.copyWith(val: value);
|
||||
void update<T>(Preference<T> preference) {
|
||||
_logger.i('updating $preference to ${preference.val}');
|
||||
|
||||
_logger.i('updating $preference to $value');
|
||||
|
||||
emit(state.copyWithPreference(updatedPreference));
|
||||
emit(state.copyWithPreference(preference));
|
||||
|
||||
switch (T) {
|
||||
case int:
|
||||
_preferenceRepository.setInt(preference.key, value as int);
|
||||
_preferenceRepository.setInt(
|
||||
preference.key,
|
||||
preference.val as int,
|
||||
);
|
||||
case double:
|
||||
_preferenceRepository.setDouble(
|
||||
preference.key,
|
||||
preference.val as double,
|
||||
);
|
||||
case bool:
|
||||
_preferenceRepository.setBool(preference.key, value as bool);
|
||||
_preferenceRepository.setBool(
|
||||
preference.key,
|
||||
preference.val as bool,
|
||||
);
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
@ -54,8 +54,6 @@ class PreferenceState extends Equatable {
|
||||
|
||||
bool get eyeCandyEnabled => _isOn<EyeCandyModePreference>();
|
||||
|
||||
bool get trueDarkEnabled => _isOn<TrueDarkModePreference>();
|
||||
|
||||
bool get readerEnabled => _isOn<ReaderModePreference>();
|
||||
|
||||
bool get markReadStoriesEnabled => _isOn<MarkReadStoriesModePreference>();
|
||||
@ -70,6 +68,17 @@ class PreferenceState extends Equatable {
|
||||
|
||||
bool get autoScrollEnabled => _isOn<AutoScrollModePreference>();
|
||||
|
||||
bool get customTabEnabled => _isOn<CustomTabPreference>();
|
||||
|
||||
double get textScaleFactor =>
|
||||
preferences.singleWhereType<TextScaleFactorPreference>().val;
|
||||
|
||||
MaterialColor get appColor {
|
||||
return materialColors.elementAt(
|
||||
preferences.singleWhereType<AppColorPreference>().val,
|
||||
) as MaterialColor;
|
||||
}
|
||||
|
||||
List<StoryType> get tabs {
|
||||
final String result =
|
||||
preferences.singleWhereType<TabOrderPreference>().val.toString();
|
||||
|
@ -38,8 +38,7 @@ class TabCubit extends Cubit<TabState> {
|
||||
// Check to make sure there's no duplicate.
|
||||
if (updatedTabs.toSet().length == StoryType.values.length) {
|
||||
_preferenceCubit.update<int>(
|
||||
TabOrderPreference(),
|
||||
to: StoryType.convertToSettingsValue(updatedTabs),
|
||||
TabOrderPreference(val: StoryType.convertToSettingsValue(updatedTabs)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hacki/config/constants.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
|
||||
extension ContextExtension on BuildContext {
|
||||
T? tryRead<T>() {
|
||||
@ -21,13 +20,18 @@ extension ContextExtension on BuildContext {
|
||||
}) {
|
||||
ScaffoldMessenger.of(this).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: Palette.deepOrange,
|
||||
content: Text(content),
|
||||
backgroundColor: Theme.of(this).primaryColor,
|
||||
content: Text(
|
||||
content,
|
||||
style: TextStyle(
|
||||
color: Theme.of(this).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
action: action != null && label != null
|
||||
? SnackBarAction(
|
||||
label: label,
|
||||
onPressed: action,
|
||||
textColor: Theme.of(this).textTheme.bodyLarge?.color,
|
||||
textColor: Theme.of(this).colorScheme.onPrimary,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
@ -33,12 +33,14 @@ extension ContextMenuBuilder on Widget {
|
||||
ContextMenuButtonItem(
|
||||
onPressed: () => LinkUtil.launch(
|
||||
'''${Constants.wikipediaLink}$selectedText''',
|
||||
context,
|
||||
),
|
||||
label: 'Wikipedia',
|
||||
),
|
||||
ContextMenuButtonItem(
|
||||
onPressed: () => LinkUtil.launch(
|
||||
'''${Constants.wiktionaryLink}$selectedText''',
|
||||
context,
|
||||
),
|
||||
label: 'Wiktionary',
|
||||
),
|
||||
|
114
lib/main.dart
114
lib/main.dart
@ -17,7 +17,6 @@ import 'package:hacki/config/constants.dart';
|
||||
import 'package:hacki/config/custom_router.dart';
|
||||
import 'package:hacki/config/locator.dart';
|
||||
import 'package:hacki/cubits/cubits.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/services/fetcher.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
import 'package:hacki/utils/theme_util.dart';
|
||||
@ -26,7 +25,6 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
||||
@ -133,12 +131,6 @@ Future<void> main({bool testing = false}) async {
|
||||
}
|
||||
|
||||
final AdaptiveThemeMode? savedThemeMode = await AdaptiveTheme.getThemeMode();
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final bool trueDarkMode =
|
||||
prefs.getBool(const TrueDarkModePreference().key) ?? false;
|
||||
final Font font = Font.values.elementAt(
|
||||
prefs.getInt(FontPreference().key) ?? Font.roboto.index,
|
||||
);
|
||||
|
||||
// Uncomment this line to log events from bloc/cubit.
|
||||
// Bloc.observer = CustomBlocObserver();
|
||||
@ -150,23 +142,17 @@ Future<void> main({bool testing = false}) async {
|
||||
runApp(
|
||||
HackiApp(
|
||||
savedThemeMode: savedThemeMode,
|
||||
trueDarkMode: trueDarkMode,
|
||||
font: font,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class HackiApp extends StatelessWidget {
|
||||
const HackiApp({
|
||||
required this.trueDarkMode,
|
||||
required this.font,
|
||||
super.key,
|
||||
this.savedThemeMode,
|
||||
});
|
||||
|
||||
final AdaptiveThemeMode? savedThemeMode;
|
||||
final Font font;
|
||||
final bool trueDarkMode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -243,56 +229,68 @@ class HackiApp extends StatelessWidget {
|
||||
)..init(),
|
||||
),
|
||||
],
|
||||
child: AdaptiveTheme(
|
||||
light: ThemeData(
|
||||
primarySwatch: Palette.orange,
|
||||
fontFamily: font.name,
|
||||
),
|
||||
dark: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
primarySwatch: Palette.orange,
|
||||
canvasColor: trueDarkMode ? Palette.black : null,
|
||||
fontFamily: font.name,
|
||||
),
|
||||
initial: savedThemeMode ?? AdaptiveThemeMode.system,
|
||||
builder: (ThemeData theme, ThemeData darkTheme) {
|
||||
final ThemeData trueDarkTheme = ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
primarySwatch: Palette.orange,
|
||||
canvasColor: Palette.black,
|
||||
fontFamily: font.name,
|
||||
);
|
||||
return FutureBuilder<AdaptiveThemeMode?>(
|
||||
future: AdaptiveTheme.getThemeMode(),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<AdaptiveThemeMode?> snapshot,
|
||||
) {
|
||||
final AdaptiveThemeMode? mode = snapshot.data;
|
||||
ThemeUtil.updateStatusBarSetting(
|
||||
SchedulerBinding.instance.platformDispatcher.platformBrightness,
|
||||
mode,
|
||||
);
|
||||
return BlocBuilder<PreferenceCubit, PreferenceState>(
|
||||
buildWhen:
|
||||
(PreferenceState previous, PreferenceState current) =>
|
||||
previous.trueDarkEnabled != current.trueDarkEnabled,
|
||||
builder: (BuildContext context, PreferenceState prefState) {
|
||||
final bool useTrueDark = prefState.trueDarkEnabled &&
|
||||
(mode == AdaptiveThemeMode.dark ||
|
||||
child: BlocBuilder<PreferenceCubit, PreferenceState>(
|
||||
buildWhen: (PreferenceState previous, PreferenceState current) =>
|
||||
previous.appColor != current.appColor ||
|
||||
previous.font != current.font ||
|
||||
previous.textScaleFactor != current.textScaleFactor,
|
||||
builder: (BuildContext context, PreferenceState state) {
|
||||
return AdaptiveTheme(
|
||||
key: ValueKey<String>('${state.appColor}${state.font}'),
|
||||
light: ThemeData(
|
||||
primaryColor: state.appColor,
|
||||
colorScheme: ColorScheme.fromSwatch(
|
||||
primarySwatch: state.appColor,
|
||||
),
|
||||
fontFamily: state.font.name,
|
||||
),
|
||||
dark: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: state.appColor,
|
||||
colorScheme: ColorScheme.fromSwatch(
|
||||
primarySwatch: state.appColor,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
canvasColor: Palette.black,
|
||||
fontFamily: state.font.name,
|
||||
),
|
||||
initial: savedThemeMode ?? AdaptiveThemeMode.system,
|
||||
builder: (ThemeData theme, ThemeData darkTheme) {
|
||||
return FutureBuilder<AdaptiveThemeMode?>(
|
||||
future: AdaptiveTheme.getThemeMode(),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<AdaptiveThemeMode?> snapshot,
|
||||
) {
|
||||
final AdaptiveThemeMode? mode = snapshot.data;
|
||||
ThemeUtil.updateStatusBarSetting(
|
||||
SchedulerBinding
|
||||
.instance.platformDispatcher.platformBrightness,
|
||||
mode,
|
||||
);
|
||||
final bool isDarkModeEnabled =
|
||||
mode == AdaptiveThemeMode.dark ||
|
||||
(mode == AdaptiveThemeMode.system &&
|
||||
View.of(context)
|
||||
.platformDispatcher
|
||||
.platformBrightness ==
|
||||
Brightness.dark));
|
||||
Brightness.dark);
|
||||
return FeatureDiscovery(
|
||||
child: MaterialApp.router(
|
||||
title: 'Hacki',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: (useTrueDark ? trueDarkTheme : theme).copyWith(
|
||||
useMaterial3: false,
|
||||
child: MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaleFactor: state.textScaleFactor == 1
|
||||
? null
|
||||
: state.textScaleFactor,
|
||||
),
|
||||
child: MaterialApp.router(
|
||||
key: Key(state.appColor.hashCode.toString()),
|
||||
title: 'Hacki',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: (isDarkModeEnabled ? darkTheme : theme).copyWith(
|
||||
useMaterial3: false,
|
||||
),
|
||||
routerConfig: router,
|
||||
),
|
||||
routerConfig: router,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -2,8 +2,10 @@ import 'dart:collection';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
|
||||
import 'package:hacki/models/displayable.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/styles/palette.dart';
|
||||
|
||||
abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
||||
const Preference({required this.val});
|
||||
@ -24,19 +26,23 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
||||
FontSizePreference(),
|
||||
TabOrderPreference(),
|
||||
StoryMarkingModePreference(),
|
||||
AppColorPreference(),
|
||||
const TextScaleFactorPreference(),
|
||||
// Order of items below matters and
|
||||
// reflects the order on settings screen.
|
||||
const DisplayModePreference(),
|
||||
const MetadataModePreference(),
|
||||
const StoryUrlModePreference(),
|
||||
// Divider.
|
||||
const MarkReadStoriesModePreference(),
|
||||
// Divider.
|
||||
const NotificationModePreference(),
|
||||
const SwipeGesturePreference(),
|
||||
const AutoScrollModePreference(),
|
||||
const CollapseModePreference(),
|
||||
const ReaderModePreference(),
|
||||
const CustomTabPreference(),
|
||||
const EyeCandyModePreference(),
|
||||
const TrueDarkModePreference(),
|
||||
],
|
||||
);
|
||||
|
||||
@ -52,20 +58,26 @@ abstract class IntPreference extends Preference<int> {
|
||||
const IntPreference({required super.val});
|
||||
}
|
||||
|
||||
abstract class DoublePreference extends Preference<double> {
|
||||
const DoublePreference({required super.val});
|
||||
}
|
||||
|
||||
const bool _notificationModeDefaultValue = true;
|
||||
const bool _swipeGestureModeDefaultValue = false;
|
||||
const bool _displayModeDefaultValue = true;
|
||||
const bool _eyeCandyModeDefaultValue = false;
|
||||
const bool _trueDarkModeDefaultValue = true;
|
||||
const bool _readerModeDefaultValue = true;
|
||||
const bool _markReadStoriesModeDefaultValue = true;
|
||||
const bool _metadataModeDefaultValue = true;
|
||||
const bool _storyUrlModeDefaultValue = true;
|
||||
const bool _collapseModeDefaultValue = true;
|
||||
const bool _autoScrollModeDefaultValue = true;
|
||||
const bool _autoScrollModeDefaultValue = false;
|
||||
const bool _customTabModeDefaultValue = false;
|
||||
const double _textScaleFactorDefaultValue = 1;
|
||||
final int _fetchModeDefaultValue = FetchMode.eager.index;
|
||||
final int _commentsOrderDefaultValue = CommentsOrder.natural.index;
|
||||
final int _fontSizeDefaultValue = FontSize.regular.index;
|
||||
final int _appColorDefaultValue = materialColors.indexOf(Palette.deepOrange);
|
||||
final int _fontDefaultValue = Font.roboto.index;
|
||||
final int _tabOrderDefaultValue =
|
||||
StoryType.convertToSettingsValue(StoryType.values);
|
||||
@ -273,23 +285,31 @@ class EyeCandyModePreference extends BooleanPreference {
|
||||
String get subtitle => 'some sort of magic.';
|
||||
}
|
||||
|
||||
class TrueDarkModePreference extends BooleanPreference {
|
||||
const TrueDarkModePreference({bool? val})
|
||||
: super(val: val ?? _trueDarkModeDefaultValue);
|
||||
/// Whether or not to use Custom Tabs for launching URLs.
|
||||
/// If false, default browser will be used.
|
||||
///
|
||||
/// https://developer.chrome.com/docs/android/custom-tabs/
|
||||
class CustomTabPreference extends BooleanPreference {
|
||||
const CustomTabPreference({bool? val})
|
||||
: super(val: val ?? _customTabModeDefaultValue);
|
||||
|
||||
@override
|
||||
TrueDarkModePreference copyWith({required bool? val}) {
|
||||
return TrueDarkModePreference(val: val);
|
||||
CustomTabPreference copyWith({required bool? val}) {
|
||||
return CustomTabPreference(val: val);
|
||||
}
|
||||
|
||||
@override
|
||||
String get key => 'trueDarkMode';
|
||||
String get key => 'customTabPreference';
|
||||
|
||||
@override
|
||||
String get title => 'True Dark Mode';
|
||||
String get title => 'Use Custom Tabs';
|
||||
|
||||
@override
|
||||
String get subtitle => 'you might need to restart the app.';
|
||||
String get subtitle =>
|
||||
'''use Custom tabs for URLs. If disabled, default browser is used instead.''';
|
||||
|
||||
@override
|
||||
bool get isDisplayable => Platform.isAndroid;
|
||||
}
|
||||
|
||||
class FetchModePreference extends IntPreference {
|
||||
@ -383,3 +403,34 @@ class StoryMarkingModePreference extends IntPreference {
|
||||
@override
|
||||
String get title => 'Mark a Story as Read on';
|
||||
}
|
||||
|
||||
class AppColorPreference extends IntPreference {
|
||||
AppColorPreference({int? val}) : super(val: val ?? _appColorDefaultValue);
|
||||
|
||||
@override
|
||||
AppColorPreference copyWith({required int? val}) {
|
||||
return AppColorPreference(val: val);
|
||||
}
|
||||
|
||||
@override
|
||||
String get key => 'appColor';
|
||||
|
||||
@override
|
||||
String get title => 'Accent Color';
|
||||
}
|
||||
|
||||
class TextScaleFactorPreference extends DoublePreference {
|
||||
const TextScaleFactorPreference({double? val})
|
||||
: super(val: val ?? _textScaleFactorDefaultValue);
|
||||
|
||||
@override
|
||||
TextScaleFactorPreference copyWith({required double? val}) {
|
||||
return TextScaleFactorPreference(val: val);
|
||||
}
|
||||
|
||||
@override
|
||||
String get key => 'appTextScaleFactor';
|
||||
|
||||
@override
|
||||
String get title => 'Default text scale factor';
|
||||
}
|
||||
|
@ -46,6 +46,10 @@ class PreferenceRepository {
|
||||
(SharedPreferences prefs) => prefs.getInt(key),
|
||||
);
|
||||
|
||||
Future<double?> getDouble(String key) => _prefs.then(
|
||||
(SharedPreferences prefs) => prefs.getDouble(key),
|
||||
);
|
||||
|
||||
//ignore: avoid_positional_boolean_parameters
|
||||
void setBool(String key, bool val) => _prefs.then(
|
||||
(SharedPreferences prefs) => prefs.setBool(key, val),
|
||||
@ -55,6 +59,10 @@ class PreferenceRepository {
|
||||
(SharedPreferences prefs) => prefs.setInt(key, val),
|
||||
);
|
||||
|
||||
void setDouble(String key, double val) => _prefs.then(
|
||||
(SharedPreferences prefs) => prefs.setDouble(key, val),
|
||||
);
|
||||
|
||||
Future<bool> hasPushed(int commentId) async =>
|
||||
_prefs.then((SharedPreferences prefs) {
|
||||
final bool? val = prefs.getBool(_getPushNotificationKey(commentId));
|
||||
|
@ -224,6 +224,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
if (story.url.isNotEmpty && isJobWithLink) {
|
||||
LinkUtil.launch(
|
||||
story.url,
|
||||
context,
|
||||
useReader: useReader,
|
||||
offlineReading: offlineReading,
|
||||
);
|
||||
|
@ -45,7 +45,7 @@ class PinnedStories extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: ColoredBox(
|
||||
color: Palette.orangeAccent.withOpacity(0.2),
|
||||
color: Theme.of(context).primaryColor.withOpacity(0.2),
|
||||
child: StoryTile(
|
||||
key: ValueKey<String>('${story.id}-PinnedStoryTile'),
|
||||
story: story,
|
||||
@ -58,10 +58,10 @@ class PinnedStories extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (state.pinnedStories.isNotEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: Dimens.pt12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: Dimens.pt12),
|
||||
child: Divider(
|
||||
color: Palette.orangeAccent,
|
||||
color: Theme.of(context).primaryColor.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -301,6 +301,8 @@ class _ItemScreenState extends State<ItemScreen>
|
||||
backgroundColor: Theme.of(context)
|
||||
.canvasColor
|
||||
.withOpacity(0.6),
|
||||
foregroundColor:
|
||||
Theme.of(context).iconTheme.color,
|
||||
item: widget.item,
|
||||
splitViewEnabled: state.enabled,
|
||||
expanded: state.expanded,
|
||||
@ -340,6 +342,7 @@ class _ItemScreenState extends State<ItemScreen>
|
||||
appBar: CustomAppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).canvasColor.withOpacity(0.6),
|
||||
foregroundColor: Theme.of(context).iconTheme.color,
|
||||
item: widget.item,
|
||||
onFontSizeTap: onFontSizeTapped,
|
||||
fontSizeIconButtonKey: fontSizeIconButtonKey,
|
||||
@ -412,17 +415,16 @@ class _ItemScreenState extends State<ItemScreen>
|
||||
fontSize: fontSize.fontSize,
|
||||
color:
|
||||
context.read<PreferenceCubit>().state.fontSize == fontSize
|
||||
? Palette.deepOrange
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
HapticFeedbackUtil.light();
|
||||
locator.get<AppReviewService>().requestReview();
|
||||
context.read<PreferenceCubit>().update(
|
||||
FontSizePreference(),
|
||||
to: fontSize.index,
|
||||
);
|
||||
context
|
||||
.read<PreferenceCubit>()
|
||||
.update(FontSizePreference(val: fontSize.index));
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -9,6 +9,7 @@ class CustomAppBar extends AppBar {
|
||||
CustomAppBar({
|
||||
required Item item,
|
||||
required super.backgroundColor,
|
||||
required super.foregroundColor,
|
||||
required VoidCallback onFontSizeTap,
|
||||
required GlobalKey fontSizeIconButtonKey,
|
||||
super.key,
|
||||
|
@ -29,7 +29,9 @@ class FavIconButton extends StatelessWidget {
|
||||
feature: DiscoverableFeature.addStoryToFavList,
|
||||
child: Icon(
|
||||
isFav ? Icons.favorite : Icons.favorite_border,
|
||||
color: isFav ? Palette.orange : Theme.of(context).iconTheme.color,
|
||||
color: isFav
|
||||
? Theme.of(context).primaryColor
|
||||
: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
|
@ -28,6 +28,7 @@ class LinkIconButton extends StatelessWidget {
|
||||
),
|
||||
onPressed: () => LinkUtil.launch(
|
||||
'https://news.ycombinator.com/item?id=$storyId',
|
||||
context,
|
||||
useHackiForHnLink: false,
|
||||
),
|
||||
);
|
||||
|
@ -35,12 +35,12 @@ class _LoginDialogState extends State<LoginDialog> with ItemActionMixin {
|
||||
return SimpleDialog(
|
||||
children: <Widget>[
|
||||
if (state.status.isLoading)
|
||||
const SizedBox(
|
||||
SizedBox(
|
||||
height: Dimens.pt36,
|
||||
width: Dimens.pt36,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -51,12 +51,13 @@ class _LoginDialogState extends State<LoginDialog> with ItemActionMixin {
|
||||
),
|
||||
child: TextField(
|
||||
controller: usernameController,
|
||||
cursorColor: Palette.orange,
|
||||
cursorColor: Theme.of(context).primaryColor,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Username',
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Palette.orange),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -70,13 +71,14 @@ class _LoginDialogState extends State<LoginDialog> with ItemActionMixin {
|
||||
),
|
||||
child: TextField(
|
||||
controller: passwordController,
|
||||
cursorColor: Palette.orange,
|
||||
cursorColor: Theme.of(context).primaryColor,
|
||||
obscureText: true,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Password',
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Palette.orange),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -108,7 +110,7 @@ class _LoginDialogState extends State<LoginDialog> with ItemActionMixin {
|
||||
? Icons.check_box
|
||||
: Icons.check_box_outline_blank,
|
||||
color: state.agreedToEULA
|
||||
? Palette.deepOrange
|
||||
? Theme.of(context).primaryColor
|
||||
: Palette.grey,
|
||||
),
|
||||
onPressed: () =>
|
||||
@ -129,11 +131,12 @@ class _LoginDialogState extends State<LoginDialog> with ItemActionMixin {
|
||||
child: TapDownWrapper(
|
||||
onTap: () => LinkUtil.launch(
|
||||
Constants.endUserAgreementLink,
|
||||
context,
|
||||
),
|
||||
child: const Text(
|
||||
child: Text(
|
||||
'End User Agreement',
|
||||
style: TextStyle(
|
||||
color: Palette.deepOrange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
decoration: TextDecoration.underline,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@ -179,7 +182,7 @@ class _LoginDialogState extends State<LoginDialog> with ItemActionMixin {
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
state.agreedToEULA
|
||||
? Palette.deepOrange
|
||||
? Theme.of(context).primaryColor
|
||||
: Palette.grey,
|
||||
),
|
||||
),
|
||||
|
@ -215,15 +215,15 @@ class _ParentItemSection extends StatelessWidget {
|
||||
}
|
||||
context.read<EditCubit>().onReplyTapped(item);
|
||||
},
|
||||
backgroundColor: Palette.orange,
|
||||
foregroundColor: Palette.white,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
icon: Icons.message,
|
||||
),
|
||||
SlidableAction(
|
||||
onPressed: (BuildContext context) =>
|
||||
onMoreTapped(item, context.rect),
|
||||
backgroundColor: Palette.orange,
|
||||
foregroundColor: Palette.white,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
icon: Icons.more_horiz,
|
||||
),
|
||||
],
|
||||
@ -239,8 +239,8 @@ class _ParentItemSection extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
item.by,
|
||||
style: const TextStyle(
|
||||
color: Palette.orange,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
textScaleFactor:
|
||||
MediaQuery.of(context).textScaleFactor,
|
||||
@ -274,6 +274,7 @@ class _ParentItemSection extends StatelessWidget {
|
||||
InkWell(
|
||||
onTap: () => LinkUtil.launch(
|
||||
item.url,
|
||||
context,
|
||||
useReader: context
|
||||
.read<PreferenceCubit>()
|
||||
.state
|
||||
@ -283,13 +284,17 @@ class _ParentItemSection extends StatelessWidget {
|
||||
.state
|
||||
.isOfflineReading,
|
||||
),
|
||||
onLongPress: () => FlutterClipboard.copy(item.url)
|
||||
.whenComplete(() {
|
||||
HapticFeedbackUtil.selection();
|
||||
context.showSnackBar(
|
||||
content: 'Link copied.',
|
||||
);
|
||||
}),
|
||||
onLongPress: () {
|
||||
if (item.url.isNotEmpty) {
|
||||
FlutterClipboard.copy(item.url)
|
||||
.whenComplete(() {
|
||||
HapticFeedbackUtil.selection();
|
||||
context.showSnackBar(
|
||||
content: 'Link copied.',
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: Dimens.pt6,
|
||||
@ -299,14 +304,6 @@ class _ParentItemSection extends StatelessWidget {
|
||||
),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: fontSize,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.color,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
semanticsLabel: item.title,
|
||||
@ -315,7 +312,7 @@ class _ParentItemSection extends StatelessWidget {
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: fontSize,
|
||||
color: item.url.isNotEmpty
|
||||
? Palette.orange
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
),
|
||||
@ -325,7 +322,8 @@ class _ParentItemSection extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: fontSize - 4,
|
||||
color: Palette.orange,
|
||||
color:
|
||||
Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -115,11 +115,15 @@ class MorePopupMenu extends StatelessWidget {
|
||||
text: HtmlUtil.parseHtml(
|
||||
state.user.about,
|
||||
),
|
||||
linkStyle: const TextStyle(
|
||||
color: Palette.orange,
|
||||
linkStyle: TextStyle(
|
||||
color:
|
||||
Theme.of(context).primaryColor,
|
||||
),
|
||||
onOpen: (LinkableElement link) =>
|
||||
LinkUtil.launch(link.url),
|
||||
LinkUtil.launch(
|
||||
link.url,
|
||||
context,
|
||||
),
|
||||
semanticsLabel: state.user.about,
|
||||
),
|
||||
actions: <Widget>[
|
||||
@ -158,12 +162,12 @@ class MorePopupMenu extends StatelessWidget {
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
FeatherIcons.chevronUp,
|
||||
color: upvoted ? Palette.orange : null,
|
||||
color: upvoted ? Theme.of(context).primaryColor : null,
|
||||
),
|
||||
title: Text(
|
||||
upvoted ? 'Upvoted' : 'Upvote',
|
||||
style: upvoted
|
||||
? const TextStyle(color: Palette.orange)
|
||||
? TextStyle(color: Theme.of(context).primaryColor)
|
||||
: null,
|
||||
),
|
||||
subtitle:
|
||||
@ -173,12 +177,12 @@ class MorePopupMenu extends StatelessWidget {
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
FeatherIcons.chevronDown,
|
||||
color: downvoted ? Palette.orange : null,
|
||||
color: downvoted ? Theme.of(context).primaryColor : null,
|
||||
),
|
||||
title: Text(
|
||||
downvoted ? 'Downvoted' : 'Downvote',
|
||||
style: downvoted
|
||||
? const TextStyle(color: Palette.orange)
|
||||
? TextStyle(color: Theme.of(context).primaryColor)
|
||||
: null,
|
||||
),
|
||||
onTap: context.read<VoteCubit>().downvote,
|
||||
@ -189,7 +193,7 @@ class MorePopupMenu extends StatelessWidget {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
isFav ? Icons.favorite : Icons.favorite_border,
|
||||
color: isFav ? Palette.orange : null,
|
||||
color: isFav ? Theme.of(context).primaryColor : null,
|
||||
),
|
||||
title: Text(
|
||||
isFav ? 'Unfavorite' : 'Favorite',
|
||||
|
@ -36,7 +36,7 @@ class PinIconButton extends StatelessWidget {
|
||||
child: Icon(
|
||||
pinned ? Icons.push_pin : Icons.push_pin_outlined,
|
||||
color: pinned
|
||||
? Palette.orange
|
||||
? Theme.of(context).primaryColor
|
||||
: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
|
@ -106,7 +106,7 @@ class _PollViewState extends State<PollView> with ItemActionMixin {
|
||||
icon: Icon(
|
||||
Icons.arrow_drop_up,
|
||||
color: voteState.vote == Vote.up
|
||||
? Palette.orange
|
||||
? Theme.of(context).primaryColor
|
||||
: Palette.grey,
|
||||
size: TextDimens.pt36,
|
||||
),
|
||||
@ -130,7 +130,7 @@ class _PollViewState extends State<PollView> with ItemActionMixin {
|
||||
),
|
||||
LinearProgressIndicator(
|
||||
value: option.ratio,
|
||||
color: Palette.deepOrange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -124,9 +124,9 @@ class _ReplyBoxState extends State<ReplyBox> with ItemActionMixin {
|
||||
duration: Durations.ms300,
|
||||
child: IconButton(
|
||||
key: const Key('quote'),
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
FeatherIcons.code,
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
size: TextDimens.pt18,
|
||||
),
|
||||
onPressed: expanded ? showTextPopup : null,
|
||||
@ -138,7 +138,7 @@ class _ReplyBoxState extends State<ReplyBox> with ItemActionMixin {
|
||||
expanded
|
||||
? FeatherIcons.minimize2
|
||||
: FeatherIcons.maximize2,
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
size: TextDimens.pt18,
|
||||
),
|
||||
onPressed: () {
|
||||
@ -150,9 +150,9 @@ class _ReplyBoxState extends State<ReplyBox> with ItemActionMixin {
|
||||
],
|
||||
IconButton(
|
||||
key: const Key('close'),
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
@ -196,8 +196,8 @@ class _ReplyBoxState extends State<ReplyBox> with ItemActionMixin {
|
||||
),
|
||||
],
|
||||
if (isLoading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: Dimens.pt12,
|
||||
horizontal: Dimens.pt16,
|
||||
),
|
||||
@ -205,7 +205,7 @@ class _ReplyBoxState extends State<ReplyBox> with ItemActionMixin {
|
||||
height: Dimens.pt24,
|
||||
width: Dimens.pt24,
|
||||
child: CircularProgressIndicator(
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
strokeWidth: Dimens.pt2,
|
||||
),
|
||||
),
|
||||
@ -213,9 +213,9 @@ class _ReplyBoxState extends State<ReplyBox> with ItemActionMixin {
|
||||
else
|
||||
IconButton(
|
||||
key: const Key('send'),
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
Icons.send,
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
widget.onSendTapped();
|
||||
@ -343,9 +343,9 @@ class _ReplyBoxState extends State<ReplyBox> with ItemActionMixin {
|
||||
).then((_) => HapticFeedbackUtil.selection()),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
size: TextDimens.pt18,
|
||||
),
|
||||
onPressed: () => context.pop(),
|
||||
|
@ -99,7 +99,8 @@ class _ProfileScreenState extends State<ProfileScreen>
|
||||
showWebPreviewOnStoryTile: false,
|
||||
showMetadataOnStoryTile: false,
|
||||
showUrl: false,
|
||||
useConsistentFontSize: true,
|
||||
showAuthor: false,
|
||||
useSimpleTileForStory: true,
|
||||
refreshController: refreshControllerHistory,
|
||||
items: historyState.submittedItems
|
||||
.where((Item e) => !e.dead && !e.deleted)
|
||||
@ -167,7 +168,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
||||
showMetadataOnStoryTile:
|
||||
prefState.metadataEnabled,
|
||||
showUrl: prefState.urlEnabled,
|
||||
useCommentTile: true,
|
||||
useSimpleTileForStory: true,
|
||||
refreshController: refreshControllerFav,
|
||||
items: favState.favItems,
|
||||
onRefresh: () {
|
||||
@ -289,9 +290,8 @@ class _ProfileScreenState extends State<ProfileScreen>
|
||||
width: Dimens.pt12,
|
||||
),
|
||||
CustomChip(
|
||||
label: 'Inbox : '
|
||||
// ignore: lines_longer_than_80_chars
|
||||
'${notificationState.unreadCommentsIds.length}',
|
||||
label:
|
||||
'''Inbox : ${notificationState.unreadCommentsIds.length}''',
|
||||
selected: pageType == PageType.notification,
|
||||
onSelected: (bool val) {
|
||||
if (val) {
|
||||
|
@ -41,8 +41,8 @@ class InboxView extends StatelessWidget {
|
||||
Expanded(
|
||||
child: SmartRefresher(
|
||||
enablePullUp: true,
|
||||
header: const WaterDropMaterialHeader(
|
||||
backgroundColor: Palette.orange,
|
||||
header: WaterDropMaterialHeader(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
footer: CustomFooter(
|
||||
loadStyle: LoadStyle.ShowWhenLoading,
|
||||
@ -80,58 +80,49 @@ class InboxView extends StatelessWidget {
|
||||
child: InkWell(
|
||||
onTap: () => onCommentTapped(e),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: Dimens.pt8,
|
||||
horizontal: Dimens.pt6,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: Dimens.pt8,
|
||||
horizontal: Dimens.pt6,
|
||||
),
|
||||
child: Linkify(
|
||||
text: '${e.by} : ${e.text}',
|
||||
style: TextStyle(
|
||||
color:
|
||||
unreadCommentsIds.contains(e.id)
|
||||
? textColor
|
||||
: Palette.grey,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color:
|
||||
unreadCommentsIds.contains(e.id)
|
||||
? Palette.orange
|
||||
: Palette.orange
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
maxLines: 4,
|
||||
onOpen: (LinkableElement link) =>
|
||||
LinkUtil.launch(link.url),
|
||||
),
|
||||
Text(
|
||||
'''${e.timeAgo} from ${e.by}:''',
|
||||
style: const TextStyle(
|
||||
color: Palette.grey,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
e.timeAgo,
|
||||
style: const TextStyle(
|
||||
color: Palette.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: Dimens.pt12,
|
||||
),
|
||||
],
|
||||
const SizedBox(
|
||||
width: Dimens.pt12,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(
|
||||
height: Dimens.zero,
|
||||
Linkify(
|
||||
text: e.text,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.copyWith(
|
||||
color: unreadCommentsIds.contains(e.id)
|
||||
? textColor
|
||||
: Palette.grey,
|
||||
fontSize: TextDimens.pt16,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryColor
|
||||
.withOpacity(
|
||||
unreadCommentsIds.contains(e.id)
|
||||
? 1
|
||||
: 0.6,
|
||||
),
|
||||
),
|
||||
maxLines: 4,
|
||||
onOpen: (LinkableElement link) =>
|
||||
LinkUtil.launch(link.url, context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -39,9 +39,15 @@ class OfflineListTile extends StatelessWidget {
|
||||
child: CustomCircularProgressIndicator(),
|
||||
);
|
||||
} else if (downloaded) {
|
||||
return const Icon(Icons.check_circle);
|
||||
return Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).primaryColor,
|
||||
);
|
||||
}
|
||||
return const Icon(Icons.download);
|
||||
return Icon(
|
||||
Icons.download,
|
||||
color: Theme.of(context).primaryColor,
|
||||
);
|
||||
}();
|
||||
|
||||
return ListTile(
|
||||
|
@ -9,6 +9,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hacki/blocs/blocs.dart';
|
||||
@ -24,6 +25,7 @@ import 'package:hacki/screens/profile/qr_code_scanner_screen.dart';
|
||||
import 'package:hacki/screens/profile/qr_code_view_screen.dart';
|
||||
import 'package:hacki/screens/profile/widgets/offline_list_tile.dart';
|
||||
import 'package:hacki/screens/profile/widgets/tab_bar_settings.dart';
|
||||
import 'package:hacki/screens/profile/widgets/text_scale_factor_settings.dart';
|
||||
import 'package:hacki/screens/widgets/widgets.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
import 'package:hacki/utils/utils.dart';
|
||||
@ -136,8 +138,9 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
if (fetchMode != null) {
|
||||
HapticFeedbackUtil.selection();
|
||||
context.read<PreferenceCubit>().update(
|
||||
FetchModePreference(),
|
||||
to: fetchMode.index,
|
||||
FetchModePreference(
|
||||
val: fetchMode.index,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -170,8 +173,9 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
if (order != null) {
|
||||
HapticFeedbackUtil.selection();
|
||||
context.read<PreferenceCubit>().update(
|
||||
CommentsOrderPreference(),
|
||||
to: order.index,
|
||||
CommentsOrderPreference(
|
||||
val: order.index,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -183,13 +187,17 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
],
|
||||
),
|
||||
const TabBarSettings(),
|
||||
const TextScaleFactorSettings(),
|
||||
const Divider(),
|
||||
StoryTile(
|
||||
showWebPreview: preferenceState.complexStoryTileEnabled,
|
||||
showMetadata: preferenceState.metadataEnabled,
|
||||
showUrl: preferenceState.urlEnabled,
|
||||
story: Story.placeholder(),
|
||||
onTap: () => LinkUtil.launch(Constants.guidelineLink),
|
||||
onTap: () => LinkUtil.launch(
|
||||
Constants.guidelineLink,
|
||||
context,
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
for (final Preference<dynamic> preference in preferenceState
|
||||
@ -211,7 +219,7 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
|
||||
context
|
||||
.read<PreferenceCubit>()
|
||||
.update(preference, to: val);
|
||||
.update(preference.copyWith(val: val));
|
||||
|
||||
if (preference is MarkReadStoriesModePreference &&
|
||||
val == false) {
|
||||
@ -220,7 +228,7 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
.add(ClearAllReadStories());
|
||||
}
|
||||
},
|
||||
activeColor: Palette.orange,
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
if (preference
|
||||
is MarkReadStoriesModePreference) ...<Widget>[
|
||||
@ -258,8 +266,9 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
if (storyMarkingMode != null) {
|
||||
HapticFeedbackUtil.selection();
|
||||
context.read<PreferenceCubit>().update(
|
||||
StoryMarkingModePreference(),
|
||||
to: storyMarkingMode.index,
|
||||
StoryMarkingModePreference(
|
||||
val: storyMarkingMode.index,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -281,6 +290,12 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
),
|
||||
onTap: showThemeSettingDialog,
|
||||
),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'Accent Color',
|
||||
),
|
||||
onTap: showColorPicker,
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
@ -385,10 +400,9 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
groupValue: state.font,
|
||||
onChanged: (Font? val) {
|
||||
if (val != null) {
|
||||
context.read<PreferenceCubit>().update(
|
||||
FontPreference(),
|
||||
to: val.index,
|
||||
);
|
||||
context
|
||||
.read<PreferenceCubit>()
|
||||
.update(FontPreference(val: val.index));
|
||||
}
|
||||
},
|
||||
title: Text(
|
||||
@ -396,18 +410,6 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
style: TextStyle(fontFamily: font.name),
|
||||
),
|
||||
),
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'*Restart required',
|
||||
style: TextStyle(
|
||||
fontSize: TextDimens.pt12,
|
||||
color: Palette.grey,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -467,6 +469,32 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
ThemeUtil.updateStatusBarSetting(brightness, val);
|
||||
}
|
||||
|
||||
void showColorPicker() {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertDialog(
|
||||
contentPadding: const EdgeInsets.all(Dimens.pt18),
|
||||
title: Text(AppColorPreference().title),
|
||||
content: MaterialColorPicker(
|
||||
colors: materialColors,
|
||||
selectedColor: context.read<PreferenceCubit>().state.appColor,
|
||||
onMainColorChange: (ColorSwatch<dynamic>? color) {
|
||||
CommentTile.levelToBorderColors.clear();
|
||||
context.read<PreferenceCubit>().update(
|
||||
AppColorPreference(
|
||||
val: materialColors.indexOf(color ?? Palette.deepOrange),
|
||||
),
|
||||
);
|
||||
context.pop();
|
||||
},
|
||||
onBack: context.pop,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void showClearCacheDialog() {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
@ -479,10 +507,10 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: const Text(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -540,6 +568,7 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
ElevatedButton(
|
||||
onPressed: () => LinkUtil.launch(
|
||||
Constants.portfolioLink,
|
||||
context,
|
||||
),
|
||||
child: const Row(
|
||||
children: <Widget>[
|
||||
@ -556,6 +585,7 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
ElevatedButton(
|
||||
onPressed: () => LinkUtil.launch(
|
||||
Constants.privacyPolicyLink,
|
||||
context,
|
||||
),
|
||||
child: const Row(
|
||||
children: <Widget>[
|
||||
@ -586,6 +616,7 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
ElevatedButton(
|
||||
onPressed: () => LinkUtil.launch(
|
||||
Constants.githubLink,
|
||||
context,
|
||||
),
|
||||
child: const Row(
|
||||
children: <Widget>[
|
||||
@ -604,6 +635,7 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
Platform.isIOS
|
||||
? Constants.appStoreLink
|
||||
: Constants.googlePlayLink,
|
||||
context,
|
||||
),
|
||||
child: const Row(
|
||||
children: <Widget>[
|
||||
@ -620,6 +652,7 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
||||
ElevatedButton(
|
||||
onPressed: () => LinkUtil.launch(
|
||||
Constants.sponsorLink,
|
||||
context,
|
||||
),
|
||||
child: const Row(
|
||||
children: <Widget>[
|
||||
|
45
lib/screens/profile/widgets/text_scale_factor_settings.dart
Normal file
45
lib/screens/profile/widgets/text_scale_factor_settings.dart
Normal file
@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hacki/cubits/cubits.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
|
||||
class TextScaleFactorSettings extends StatelessWidget {
|
||||
const TextScaleFactorSettings({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<PreferenceCubit, PreferenceState>(
|
||||
buildWhen: (PreferenceState previous, PreferenceState current) =>
|
||||
previous.textScaleFactor != current.textScaleFactor,
|
||||
builder: (BuildContext context, PreferenceState state) {
|
||||
final String label = state.textScaleFactor == 1
|
||||
? '''system default ${MediaQuery.of(context).textScaleFactor.toStringAsPrecision(2)}'''
|
||||
: state.textScaleFactor.toString();
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
width: Dimens.pt16,
|
||||
),
|
||||
Text('Text scale factor: $label'),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
Slider(
|
||||
value: state.textScaleFactor,
|
||||
min: 0.8,
|
||||
max: 1.5,
|
||||
divisions: 7,
|
||||
label: label,
|
||||
onChanged: (double value) => context
|
||||
.read<PreferenceCubit>()
|
||||
.update(TextScaleFactorPreference(val: value)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -3,3 +3,4 @@ export 'inbox_view.dart';
|
||||
export 'offline_list_tile.dart';
|
||||
export 'settings.dart';
|
||||
export 'tab_bar_settings.dart';
|
||||
export 'text_scale_factor_settings.dart';
|
||||
|
@ -67,12 +67,14 @@ class _SearchScreenState extends State<SearchScreen> with ItemActionMixin {
|
||||
horizontal: Dimens.pt12,
|
||||
),
|
||||
child: TextField(
|
||||
cursorColor: Palette.orange,
|
||||
cursorColor: Theme.of(context).primaryColor,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search Hacker News',
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Palette.orange),
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (String val) {
|
||||
@ -275,8 +277,8 @@ class _SearchScreenState extends State<SearchScreen> with ItemActionMixin {
|
||||
child: SmartRefresher(
|
||||
enablePullDown: false,
|
||||
enablePullUp: true,
|
||||
header: const WaterDropMaterialHeader(
|
||||
backgroundColor: Palette.orange,
|
||||
header: WaterDropMaterialHeader(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
footer: CustomFooter(
|
||||
loadStyle: LoadStyle.ShowWhenLoading,
|
||||
|
@ -48,12 +48,13 @@ class PostedByFilterChip extends StatelessWidget {
|
||||
),
|
||||
child: TextField(
|
||||
controller: usernameController,
|
||||
cursorColor: Palette.orange,
|
||||
cursorColor: Theme.of(context).primaryColor,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Username',
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Palette.orange),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -85,8 +86,9 @@ class PostedByFilterChip extends StatelessWidget {
|
||||
context.pop(text.isEmpty ? null : text);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all(Palette.deepOrange),
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Confirm',
|
||||
|
@ -88,8 +88,8 @@ class _SubmitScreenState extends State<SubmitScreen> with ItemActionMixin {
|
||||
),
|
||||
actions: <Widget>[
|
||||
if (state.status == Status.inProgress)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: Dimens.pt18,
|
||||
horizontal: Dimens.pt16,
|
||||
),
|
||||
@ -97,16 +97,16 @@ class _SubmitScreenState extends State<SubmitScreen> with ItemActionMixin {
|
||||
height: Dimens.pt20,
|
||||
width: Dimens.pt20,
|
||||
child: CircularProgressIndicator(
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (canSubmit())
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
Icons.send,
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed: context.read<SubmitCubit>().onSubmitTapped,
|
||||
)
|
||||
@ -128,13 +128,14 @@ class _SubmitScreenState extends State<SubmitScreen> with ItemActionMixin {
|
||||
),
|
||||
child: TextField(
|
||||
controller: titleEditingController,
|
||||
cursorColor: Palette.orange,
|
||||
cursorColor: Theme.of(context).primaryColor,
|
||||
autocorrect: false,
|
||||
maxLength: 80,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Title',
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Palette.orange),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
),
|
||||
onChanged: context.read<SubmitCubit>().onTitleChanged,
|
||||
@ -147,12 +148,13 @@ class _SubmitScreenState extends State<SubmitScreen> with ItemActionMixin {
|
||||
child: TextField(
|
||||
enabled: textEditingController.text.isEmpty,
|
||||
controller: urlEditingController,
|
||||
cursorColor: Palette.orange,
|
||||
cursorColor: Theme.of(context).primaryColor,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Url',
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Palette.orange),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
),
|
||||
onChanged: context.read<SubmitCubit>().onUrlChanged,
|
||||
@ -174,7 +176,7 @@ class _SubmitScreenState extends State<SubmitScreen> with ItemActionMixin {
|
||||
child: TextField(
|
||||
enabled: urlEditingController.text.isEmpty,
|
||||
controller: textEditingController,
|
||||
cursorColor: Palette.orange,
|
||||
cursorColor: Theme.of(context).primaryColor,
|
||||
maxLines: 200,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Text',
|
||||
|
@ -47,7 +47,7 @@ class CommentTile extends StatelessWidget {
|
||||
/// Override for search screen.
|
||||
final VoidCallback? onTap;
|
||||
|
||||
static final Map<int, Color> _colors = <int, Color>{};
|
||||
static final Map<int, Color> levelToBorderColors = <int, Color>{};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -68,8 +68,12 @@ class CommentTile extends StatelessWidget {
|
||||
) {
|
||||
if (actionable && state.hidden) return const SizedBox.shrink();
|
||||
|
||||
const Color orange = Color.fromRGBO(255, 152, 0, 1);
|
||||
final Color color = _getColor(level);
|
||||
final MaterialColor primaryColor =
|
||||
context.read<PreferenceCubit>().state.appColor;
|
||||
final Color color = _getColor(
|
||||
level,
|
||||
primaryColor: primaryColor,
|
||||
);
|
||||
|
||||
final Widget child = DeviceGestureWrapper(
|
||||
child: Column(
|
||||
@ -82,16 +86,18 @@ class CommentTile extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
SlidableAction(
|
||||
onPressed: (_) => onReplyTapped?.call(comment),
|
||||
backgroundColor: Palette.orange,
|
||||
foregroundColor: Palette.white,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
icon: Icons.message,
|
||||
),
|
||||
if (context.read<AuthBloc>().state.user.id ==
|
||||
comment.by)
|
||||
SlidableAction(
|
||||
onPressed: (_) => onEditTapped?.call(comment),
|
||||
backgroundColor: Palette.orange,
|
||||
foregroundColor: Palette.white,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
icon: Icons.edit,
|
||||
),
|
||||
SlidableAction(
|
||||
@ -100,8 +106,9 @@ class CommentTile extends StatelessWidget {
|
||||
comment,
|
||||
context.rect,
|
||||
),
|
||||
backgroundColor: Palette.orange,
|
||||
foregroundColor: Palette.white,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
icon: Icons.more_horiz,
|
||||
),
|
||||
],
|
||||
@ -114,8 +121,9 @@ class CommentTile extends StatelessWidget {
|
||||
SlidableAction(
|
||||
onPressed: (_) =>
|
||||
onRightMoreTapped?.call(comment),
|
||||
backgroundColor: Palette.orange,
|
||||
foregroundColor: Palette.white,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
icon: Icons.av_timer,
|
||||
),
|
||||
],
|
||||
@ -143,18 +151,16 @@ class CommentTile extends StatelessWidget {
|
||||
Text(
|
||||
comment.by,
|
||||
style: TextStyle(
|
||||
color: prefState.eyeCandyEnabled
|
||||
? orange
|
||||
: color,
|
||||
color: primaryColor,
|
||||
),
|
||||
textScaleFactor:
|
||||
MediaQuery.of(context).textScaleFactor,
|
||||
),
|
||||
if (comment.by == opUsername)
|
||||
const Text(
|
||||
Text(
|
||||
' - OP',
|
||||
style: TextStyle(
|
||||
color: orange,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
@ -178,7 +184,9 @@ class CommentTile extends StatelessWidget {
|
||||
CenteredText(
|
||||
text:
|
||||
'''collapsed (${state.collapsedCount + 1})''',
|
||||
color: Palette.orangeAccent,
|
||||
color: Theme.of(context)
|
||||
.primaryColor
|
||||
.withOpacity(0.8),
|
||||
)
|
||||
else if (comment.hidden)
|
||||
const CenteredText.hidden()
|
||||
@ -276,14 +284,17 @@ class CommentTile extends StatelessWidget {
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Palette.orange.withOpacity(0.2),
|
||||
color: Theme.of(context).primaryColor.withOpacity(0.2),
|
||||
),
|
||||
child: wrapper,
|
||||
);
|
||||
}
|
||||
|
||||
for (final int i in level.to(0, inclusive: false)) {
|
||||
final Color wrapperBorderColor = _getColor(i);
|
||||
final Color wrapperBorderColor = _getColor(
|
||||
i,
|
||||
primaryColor: primaryColor,
|
||||
);
|
||||
final bool shouldHighlight = isMyComment && i == level;
|
||||
wrapper = Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
@ -299,7 +310,7 @@ class CommentTile extends StatelessWidget {
|
||||
)
|
||||
: null,
|
||||
color: shouldHighlight
|
||||
? Palette.orange.withOpacity(0.2)
|
||||
? primaryColor.withOpacity(0.2)
|
||||
: commentColor,
|
||||
),
|
||||
child: wrapper,
|
||||
@ -312,31 +323,27 @@ class CommentTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Color _getColor(int level) {
|
||||
Color _getColor(
|
||||
int level, {
|
||||
required MaterialColor primaryColor,
|
||||
}) {
|
||||
final int initialLevel = level;
|
||||
if (_colors[initialLevel] != null) return _colors[initialLevel]!;
|
||||
|
||||
if (levelToBorderColors[initialLevel] != null) {
|
||||
return levelToBorderColors[initialLevel]!;
|
||||
} else if (level == 0) {
|
||||
levelToBorderColors[initialLevel] = primaryColor;
|
||||
return primaryColor;
|
||||
}
|
||||
|
||||
while (level >= 10) {
|
||||
level = level - 10;
|
||||
}
|
||||
|
||||
const int r = 255;
|
||||
int g = level * 40 < 255 ? 152 : (level * 20).clamp(0, 255);
|
||||
int b = (level * 40).clamp(0, 255);
|
||||
final double opacity = ((10 - level) / 10).clamp(0.3, 1);
|
||||
final Color color = primaryColor.withOpacity(opacity);
|
||||
|
||||
if (g == 255 && b == 255) {
|
||||
g = (level * 30 - 255).clamp(0, 255);
|
||||
b = (level * 40 - 255).clamp(0, 255);
|
||||
}
|
||||
|
||||
final Color color = Color.fromRGBO(
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
1,
|
||||
);
|
||||
|
||||
_colors[initialLevel] = color;
|
||||
levelToBorderColors[initialLevel] = color;
|
||||
return color;
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ class _CountDownReminderState extends State<CountdownReminder>
|
||||
animation: animationController,
|
||||
child: FadeIn(
|
||||
child: Material(
|
||||
color: Palette.deepOrange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(
|
||||
@ -125,8 +125,8 @@ class _CountDownReminderState extends State<CountdownReminder>
|
||||
},
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: Dimens.pt12,
|
||||
top: Dimens.pt10,
|
||||
right: Dimens.pt10,
|
||||
@ -136,15 +136,15 @@ class _CountDownReminderState extends State<CountdownReminder>
|
||||
Text(
|
||||
'Pick up where you left off',
|
||||
style: TextStyle(
|
||||
color: Palette.white,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontSize: TextDimens.pt12,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
const Spacer(),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: TextDimens.pt12,
|
||||
color: Palette.white,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -19,13 +19,17 @@ class CustomChip extends StatelessWidget {
|
||||
shadowColor: Palette.transparent,
|
||||
selectedShadowColor: Palette.transparent,
|
||||
backgroundColor: Palette.transparent,
|
||||
shape: const StadiumBorder(
|
||||
side: BorderSide(color: Palette.orange),
|
||||
shape: StadiumBorder(
|
||||
side: BorderSide(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
label: Text(label),
|
||||
labelStyle: TextStyle(
|
||||
color: selected ? Theme.of(context).colorScheme.onPrimary : null,
|
||||
),
|
||||
checkmarkColor: selected ? Theme.of(context).colorScheme.onPrimary : null,
|
||||
selected: selected,
|
||||
onSelected: onSelected,
|
||||
selectedColor: Palette.orange,
|
||||
selectedColor: Theme.of(context).primaryColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
|
||||
/// Circular progress indicator with color.
|
||||
/// Changing `colorScheme`'s `primary` color doesn't work because it changes
|
||||
@ -18,7 +17,7 @@ class CustomCircularProgressIndicator extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return CircularProgressIndicator(
|
||||
strokeWidth: strokeWidth,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(Palette.orange),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hacki/cubits/cubits.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/screens/widgets/custom_linkify/linkifiers/linkifiers.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
import 'package:hacki/utils/utils.dart';
|
||||
import 'package:linkify/linkify.dart' hide UrlLinkifier;
|
||||
|
||||
@ -36,7 +37,7 @@ class Linkify extends StatelessWidget {
|
||||
this.textDirection,
|
||||
this.maxLines,
|
||||
this.overflow = TextOverflow.clip,
|
||||
this.textScaleFactor = 1.0,
|
||||
this.textScaleFactor,
|
||||
this.softWrap = true,
|
||||
this.strutStyle,
|
||||
this.locale,
|
||||
@ -79,7 +80,7 @@ class Linkify extends StatelessWidget {
|
||||
final TextOverflow overflow;
|
||||
|
||||
/// The number of font pixels for each logical pixel
|
||||
final double textScaleFactor;
|
||||
final double? textScaleFactor;
|
||||
|
||||
/// Whether the text should break at soft line breaks.
|
||||
final bool softWrap;
|
||||
@ -109,6 +110,7 @@ class Linkify extends StatelessWidget {
|
||||
return Text.rich(
|
||||
buildTextSpan(
|
||||
elements,
|
||||
primaryColor: context.read<PreferenceCubit>().state.appColor,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.merge(style),
|
||||
onOpen: onOpen,
|
||||
useMouseRegion: true,
|
||||
@ -168,7 +170,7 @@ class SelectableLinkify extends StatelessWidget {
|
||||
this.maxLines,
|
||||
// SelectableText
|
||||
this.focusNode,
|
||||
this.textScaleFactor = 1.0,
|
||||
this.textScaleFactor,
|
||||
this.strutStyle,
|
||||
this.showCursor = false,
|
||||
this.autofocus = false,
|
||||
@ -193,7 +195,7 @@ class SelectableLinkify extends StatelessWidget {
|
||||
final String? semanticsLabel;
|
||||
|
||||
/// The number of font pixels for each logical pixel
|
||||
final double textScaleFactor;
|
||||
final double? textScaleFactor;
|
||||
|
||||
/// Linkifiers to be used for linkify
|
||||
final List<Linkifier> linkifiers;
|
||||
@ -287,6 +289,7 @@ class SelectableLinkify extends StatelessWidget {
|
||||
return SelectableText.rich(
|
||||
buildTextSpan(
|
||||
elements,
|
||||
primaryColor: context.read<PreferenceCubit>().state.appColor,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.merge(style),
|
||||
onOpen: onOpen,
|
||||
linkStyle: Theme.of(context)
|
||||
@ -352,6 +355,7 @@ class LinkableSpan extends WidgetSpan {
|
||||
/// Raw TextSpan builder for more control on the RichText
|
||||
TextSpan buildTextSpan(
|
||||
List<LinkifyElement> elements, {
|
||||
required MaterialColor primaryColor,
|
||||
TextStyle? style,
|
||||
TextStyle? linkStyle,
|
||||
LinkCallback? onOpen,
|
||||
@ -386,7 +390,7 @@ TextSpan buildTextSpan(
|
||||
return TextSpan(
|
||||
text: element.text,
|
||||
style: style?.copyWith(
|
||||
backgroundColor: Palette.orangeAccent.withOpacity(0.3),
|
||||
backgroundColor: primaryColor.withOpacity(0.3),
|
||||
),
|
||||
);
|
||||
} else if (element is EmphasisElement) {
|
||||
|
@ -43,9 +43,9 @@ class _CustomTabBarState extends State<CustomTabBar> {
|
||||
return TabBar(
|
||||
isScrollable: true,
|
||||
controller: widget.tabController,
|
||||
indicatorColor: Palette.orange,
|
||||
indicatorColor: Theme.of(context).primaryColor,
|
||||
indicator: CircleTabIndicator(
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
radius: Dimens.pt2,
|
||||
),
|
||||
indicatorPadding: const EdgeInsets.only(
|
||||
@ -64,7 +64,9 @@ class _CustomTabBarState extends State<CustomTabBar> {
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
currentIndex == i ? TextDimens.pt14 : TextDimens.pt10,
|
||||
color: currentIndex == i ? Palette.orange : Palette.grey,
|
||||
color: currentIndex == i
|
||||
? Theme.of(context).primaryColor
|
||||
: Palette.grey,
|
||||
),
|
||||
duration: Durations.ms200,
|
||||
child: Text(
|
||||
@ -110,8 +112,9 @@ class _CustomTabBarState extends State<CustomTabBar> {
|
||||
size: currentIndex == 5
|
||||
? TextDimens.pt16
|
||||
: TextDimens.pt12,
|
||||
color:
|
||||
currentIndex == 5 ? Palette.orange : Palette.grey,
|
||||
color: currentIndex == 5
|
||||
? Theme.of(context).primaryColor
|
||||
: Palette.grey,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -4,7 +4,6 @@ import 'package:hacki/cubits/cubits.dart';
|
||||
import 'package:hacki/extensions/extensions.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/screens/widgets/widgets.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
import 'package:hacki/utils/utils.dart';
|
||||
|
||||
class ItemText extends StatelessWidget {
|
||||
@ -33,7 +32,7 @@ class ItemText extends StatelessWidget {
|
||||
final TextStyle linkStyle = TextStyle(
|
||||
fontSize: prefState.fontSize.fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
color: Palette.orange,
|
||||
color: Theme.of(context).primaryColor,
|
||||
);
|
||||
|
||||
void onSelectionChanged(
|
||||
@ -50,9 +49,13 @@ class ItemText extends StatelessWidget {
|
||||
return SelectableText.rich(
|
||||
buildTextSpan(
|
||||
(item as Buildable).elements,
|
||||
primaryColor: context.read<PreferenceCubit>().state.appColor,
|
||||
style: style,
|
||||
linkStyle: linkStyle,
|
||||
onOpen: (LinkableElement link) => LinkUtil.launch(link.url),
|
||||
onOpen: (LinkableElement link) => LinkUtil.launch(
|
||||
link.url,
|
||||
context,
|
||||
),
|
||||
),
|
||||
onTap: onTap,
|
||||
textScaleFactor: textScaleFactor,
|
||||
@ -74,9 +77,13 @@ class ItemText extends StatelessWidget {
|
||||
child: Text.rich(
|
||||
buildTextSpan(
|
||||
(item as Buildable).elements,
|
||||
primaryColor: context.read<PreferenceCubit>().state.appColor,
|
||||
style: style,
|
||||
linkStyle: linkStyle,
|
||||
onOpen: (LinkableElement link) => LinkUtil.launch(link.url),
|
||||
onOpen: (LinkableElement link) => LinkUtil.launch(
|
||||
link.url,
|
||||
context,
|
||||
),
|
||||
),
|
||||
textScaleFactor: textScaleFactor,
|
||||
semanticsLabel: item.text,
|
||||
@ -89,7 +96,10 @@ class ItemText extends StatelessWidget {
|
||||
textScaleFactor: textScaleFactor,
|
||||
style: style,
|
||||
linkStyle: linkStyle,
|
||||
onOpen: (LinkableElement link) => LinkUtil.launch(link.url),
|
||||
onOpen: (LinkableElement link) => LinkUtil.launch(
|
||||
link.url,
|
||||
context,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -20,11 +20,10 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
||||
required this.onTap,
|
||||
required this.refreshController,
|
||||
super.key,
|
||||
this.useCommentTile = false,
|
||||
this.showCommentBy = false,
|
||||
this.showAuthor = true,
|
||||
this.useSimpleTileForStory = false,
|
||||
this.enablePullDown = true,
|
||||
this.markReadStories = false,
|
||||
this.useConsistentFontSize = false,
|
||||
this.showOfflineBanner = false,
|
||||
this.onRefresh,
|
||||
this.onLoadMore,
|
||||
@ -35,8 +34,8 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
||||
this.itemBuilder,
|
||||
});
|
||||
|
||||
final bool useCommentTile;
|
||||
final bool showCommentBy;
|
||||
final bool showAuthor;
|
||||
final bool useSimpleTileForStory;
|
||||
final bool showWebPreviewOnStoryTile;
|
||||
final bool showMetadataOnStoryTile;
|
||||
final bool showUrl;
|
||||
@ -44,9 +43,6 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
||||
final bool markReadStories;
|
||||
final bool showOfflineBanner;
|
||||
|
||||
/// Whether to use same font size for comment and story tiles.
|
||||
final bool useConsistentFontSize;
|
||||
|
||||
final List<T> items;
|
||||
final Widget? header;
|
||||
final RefreshController refreshController;
|
||||
@ -76,116 +72,146 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
||||
final bool swipeGestureEnabled =
|
||||
context.read<PreferenceCubit>().state.swipeGestureEnabled;
|
||||
return <Widget>[
|
||||
GestureDetector(
|
||||
/// If swipe gesture is enabled on home screen, use long press
|
||||
/// instead of slide action to trigger the action menu.
|
||||
onLongPress: swipeGestureEnabled
|
||||
? () => onMoreTapped?.call(e, context.rect)
|
||||
: null,
|
||||
child: FadeIn(
|
||||
child: StoryTile(
|
||||
key: ValueKey<int>(e.id),
|
||||
story: e,
|
||||
if (useSimpleTileForStory)
|
||||
FadeIn(
|
||||
child: InkWell(
|
||||
onTap: () => onTap(e),
|
||||
showWebPreview: showWebPreviewOnStoryTile,
|
||||
showMetadata: showMetadataOnStoryTile,
|
||||
showUrl: showUrl,
|
||||
hasRead: markReadStories && hasRead,
|
||||
simpleTileFontSize: useConsistentFontSize
|
||||
? TextDimens.pt14
|
||||
: TextDimens.pt16,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: Dimens.pt8,
|
||||
bottom: Dimens.pt8,
|
||||
left: Dimens.pt12,
|
||||
right: Dimens.pt6,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
showAuthor
|
||||
? '''${e.timeAgo} by ${e.by}'''
|
||||
: e.timeAgo,
|
||||
style: const TextStyle(
|
||||
color: Palette.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: Dimens.pt12,
|
||||
),
|
||||
],
|
||||
),
|
||||
Linkify(
|
||||
text: e.title,
|
||||
maxLines: 4,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontSize: TextDimens.pt16,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
onOpen: (LinkableElement link) => LinkUtil.launch(
|
||||
link.url,
|
||||
context,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
GestureDetector(
|
||||
/// If swipe gesture is enabled on home screen, use long press
|
||||
/// instead of slide action to trigger the action menu.
|
||||
onLongPress: swipeGestureEnabled
|
||||
? () => onMoreTapped?.call(e, context.rect)
|
||||
: null,
|
||||
child: FadeIn(
|
||||
child: StoryTile(
|
||||
key: ValueKey<int>(e.id),
|
||||
story: e,
|
||||
onTap: () => onTap(e),
|
||||
showWebPreview: showWebPreviewOnStoryTile,
|
||||
showMetadata: showMetadataOnStoryTile,
|
||||
showUrl: showUrl,
|
||||
hasRead: markReadStories && hasRead,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!showWebPreviewOnStoryTile)
|
||||
const Divider(
|
||||
height: Dimens.zero,
|
||||
),
|
||||
];
|
||||
} else if (e is Comment) {
|
||||
if (useCommentTile) {
|
||||
return <Widget>[
|
||||
if (showWebPreviewOnStoryTile)
|
||||
const Divider(
|
||||
height: Dimens.zero,
|
||||
),
|
||||
_CommentTile(
|
||||
comment: e,
|
||||
onTap: () => onTap(e),
|
||||
fontSize: showWebPreviewOnStoryTile
|
||||
? TextDimens.pt14
|
||||
: TextDimens.pt16,
|
||||
),
|
||||
const Divider(
|
||||
height: Dimens.zero,
|
||||
),
|
||||
];
|
||||
}
|
||||
return <Widget>[
|
||||
FadeIn(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: Dimens.pt6),
|
||||
child: InkWell(
|
||||
onTap: () => onTap(e),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (e.deleted)
|
||||
const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: Dimens.pt6,
|
||||
),
|
||||
child: Text(
|
||||
'deleted',
|
||||
style: TextStyle(color: Palette.grey),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => onTap(e),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: Dimens.pt8,
|
||||
bottom: Dimens.pt8,
|
||||
left: Dimens.pt12,
|
||||
right: Dimens.pt6,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (e.deleted)
|
||||
const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: Dimens.pt6,
|
||||
),
|
||||
child: Text(
|
||||
'deleted',
|
||||
style: TextStyle(color: Palette.grey),
|
||||
),
|
||||
),
|
||||
Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: Dimens.pt8,
|
||||
horizontal: Dimens.pt6,
|
||||
),
|
||||
child: Linkify(
|
||||
text:
|
||||
'''${showCommentBy ? '${e.by}: ' : ''}${e.text}''',
|
||||
maxLines: 4,
|
||||
linkStyle: const TextStyle(
|
||||
color: Palette.orange,
|
||||
),
|
||||
onOpen: (LinkableElement link) =>
|
||||
LinkUtil.launch(link.url),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
showAuthor
|
||||
? '''${e.timeAgo} by ${e.by}'''
|
||||
: e.timeAgo,
|
||||
style: const TextStyle(
|
||||
color: Palette.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
e.timeAgo,
|
||||
style: const TextStyle(
|
||||
color: Palette.grey,
|
||||
),
|
||||
const SizedBox(
|
||||
width: Dimens.pt12,
|
||||
),
|
||||
],
|
||||
),
|
||||
Linkify(
|
||||
text: e.text,
|
||||
maxLines: 4,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.copyWith(
|
||||
fontSize: TextDimens.pt16,
|
||||
),
|
||||
const SizedBox(
|
||||
width: Dimens.pt12,
|
||||
),
|
||||
],
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(
|
||||
height: Dimens.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
onOpen: (LinkableElement link) => LinkUtil.launch(
|
||||
link.url,
|
||||
context,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -211,8 +237,8 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
||||
return SmartRefresher(
|
||||
enablePullUp: true,
|
||||
enablePullDown: enablePullDown,
|
||||
header: const WaterDropMaterialHeader(
|
||||
backgroundColor: Palette.orange,
|
||||
header: WaterDropMaterialHeader(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
footer: CustomFooter(
|
||||
loadStyle: LoadStyle.ShowWhenLoading,
|
||||
@ -242,66 +268,3 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CommentTile extends StatelessWidget {
|
||||
const _CommentTile({
|
||||
required this.comment,
|
||||
required this.onTap,
|
||||
this.fontSize = 16,
|
||||
});
|
||||
|
||||
final Comment comment;
|
||||
final VoidCallback onTap;
|
||||
final double fontSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: Dimens.pt12,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
height: Dimens.pt8,
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
comment.text.trimLeft(),
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
comment.metadata,
|
||||
style: TextStyle(
|
||||
color: Palette.grey,
|
||||
fontSize: fontSize - 2,
|
||||
),
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: Dimens.pt8,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +196,7 @@ class LinkView extends StatelessWidget {
|
||||
if (url.isNotEmpty) {
|
||||
LinkUtil.launch(
|
||||
url,
|
||||
context,
|
||||
useHackiForHnLink: false,
|
||||
offlineReading:
|
||||
context.read<StoriesBloc>().state.isOfflineReading,
|
||||
|
@ -27,7 +27,7 @@ class OfflineBanner extends StatelessWidget {
|
||||
'${showExitButton ? 'Exit to fetch latest stories.' : ''}',
|
||||
textAlign: showExitButton ? TextAlign.left : TextAlign.center,
|
||||
),
|
||||
backgroundColor: Palette.orangeAccent.withOpacity(0.3),
|
||||
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.3),
|
||||
actions: <Widget>[
|
||||
if (showExitButton)
|
||||
TextButton(
|
||||
|
@ -24,7 +24,7 @@ class _OnboardingViewState extends State<OnboardingView> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).brightness == Brightness.light
|
||||
? Palette.orange
|
||||
? Theme.of(context).primaryColor
|
||||
: Theme.of(context).canvasColor,
|
||||
elevation: Dimens.zero,
|
||||
leading: IconButton(
|
||||
@ -36,7 +36,7 @@ class _OnboardingViewState extends State<OnboardingView> {
|
||||
),
|
||||
),
|
||||
backgroundColor: Theme.of(context).brightness == Brightness.light
|
||||
? Palette.orange
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
@ -89,7 +89,7 @@ class _OnboardingViewState extends State<OnboardingView> {
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
backgroundColor: Palette.orange,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
padding: const EdgeInsets.all(
|
||||
Dimens.pt18,
|
||||
),
|
||||
|
@ -7,7 +7,6 @@ import 'package:hacki/cubits/cubits.dart';
|
||||
import 'package:hacki/extensions/extensions.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/screens/widgets/widgets.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
import 'package:hacki/utils/utils.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
@ -107,8 +106,9 @@ class _StoriesListViewState extends State<StoriesListView>
|
||||
HapticFeedbackUtil.light();
|
||||
context.read<PinCubit>().pinStory(story);
|
||||
},
|
||||
backgroundColor: Palette.orange,
|
||||
foregroundColor: Palette.white,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
icon: preferenceState.complexStoryTileEnabled
|
||||
? Icons.push_pin_outlined
|
||||
: null,
|
||||
@ -118,8 +118,9 @@ class _StoriesListViewState extends State<StoriesListView>
|
||||
),
|
||||
SlidableAction(
|
||||
onPressed: (_) => onMoreTapped(story, context.rect),
|
||||
backgroundColor: Palette.orange,
|
||||
foregroundColor: Palette.white,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
icon: preferenceState.complexStoryTileEnabled
|
||||
? Icons.more_horiz
|
||||
: null,
|
||||
|
@ -154,8 +154,8 @@ class _LinkPreviewPlaceholder extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: Shimmer.fromColors(
|
||||
baseColor: Palette.orange,
|
||||
highlightColor: Palette.orangeAccent,
|
||||
baseColor: Theme.of(context).primaryColor,
|
||||
highlightColor: Theme.of(context).primaryColor.withOpacity(0.8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
|
@ -1,14 +1,16 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:hacki/config/custom_router.dart';
|
||||
import 'package:hacki/config/locator.dart';
|
||||
import 'package:hacki/cubits/cubits.dart';
|
||||
import 'package:hacki/extensions/extensions.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/repositories/repositories.dart';
|
||||
import 'package:hacki/screens/screens.dart'
|
||||
show ItemScreen, ItemScreenArgs, WebViewScreen;
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
abstract class LinkUtil {
|
||||
@ -25,7 +27,8 @@ abstract class LinkUtil {
|
||||
}
|
||||
|
||||
static void launch(
|
||||
String link, {
|
||||
String link,
|
||||
BuildContext context, {
|
||||
bool useReader = false,
|
||||
bool offlineReading = false,
|
||||
bool useHackiForHnLink = true,
|
||||
@ -47,24 +50,40 @@ abstract class LinkUtil {
|
||||
}
|
||||
|
||||
if (useHackiForHnLink && link.isStoryLink) {
|
||||
_onStoryLinkTapped(link);
|
||||
return;
|
||||
final int? id = link.itemId;
|
||||
if (id != null) {
|
||||
locator.get<StoriesRepository>().fetchItem(id: id).then((Item? item) {
|
||||
if (item != null) {
|
||||
router.push(
|
||||
'/${ItemScreen.routeName}',
|
||||
extra: ItemScreenArgs(item: item),
|
||||
);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Uri uri = Uri.parse(link);
|
||||
|
||||
canLaunchUrl(uri).then((bool val) {
|
||||
if (val) {
|
||||
if (link.contains('http')) {
|
||||
if (Platform.isAndroid) {
|
||||
if (Platform.isAndroid &&
|
||||
context.read<PreferenceCubit>().state.customTabEnabled == false) {
|
||||
launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
final Color primaryColor = Theme.of(context).primaryColor;
|
||||
_browser
|
||||
.open(
|
||||
url: uri,
|
||||
options: ChromeSafariBrowserClassOptions(
|
||||
ios: IOSSafariOptions(
|
||||
entersReaderIfAvailable: useReader,
|
||||
preferredControlTintColor: Palette.orange,
|
||||
preferredControlTintColor: primaryColor,
|
||||
),
|
||||
android: AndroidChromeCustomTabsOptions(
|
||||
toolbarBackgroundColor: primaryColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -76,23 +95,4 @@ abstract class LinkUtil {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _onStoryLinkTapped(String link) async {
|
||||
final int? id = link.itemId;
|
||||
if (id != null) {
|
||||
await locator
|
||||
.get<StoriesRepository>()
|
||||
.fetchItem(id: id)
|
||||
.then((Item? item) {
|
||||
if (item != null) {
|
||||
router.push(
|
||||
'/${ItemScreen.routeName}',
|
||||
extra: ItemScreenArgs(item: item),
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
launch(link, useHackiForHnLink: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
54
pubspec.lock
54
pubspec.lock
@ -133,10 +133,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b"
|
||||
sha256: b502a681ba415272ecc41400bd04fe543ed1a62632137dc84d25a91e7746f55f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "5.0.1"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -197,10 +197,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659"
|
||||
sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.3"
|
||||
version: "9.1.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -328,18 +328,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_inappwebview
|
||||
sha256: f73505c792cf083d5566e1a94002311be497d984b5607f25be36d685cf6361cf
|
||||
sha256: d198297060d116b94048301ee6749cd2e7d03c1f2689783f52d210a6b7aba350
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.2+3"
|
||||
version: "5.8.0"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "3002092e5b8ce2f86c3361422e52e6db6776c23ee21e0b2f71b892bf4259ef04"
|
||||
sha256: "6d11ea777496061e583623aaf31923f93a9409ef8fcaeeefdd6cd78bf4fe5bb3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.1.1"
|
||||
version: "16.1.0"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -356,6 +356,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0+1"
|
||||
flutter_material_color_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_material_color_picker
|
||||
sha256: ca1e7749d228c9155ea24bce98e647cdbffa350e6f334f6c001f841cd3d9c987
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -479,10 +487,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: a07c781bf55bf11ae85133338e4850f0b4e33e261c44a66c750fc707d65d8393
|
||||
sha256: a206cc4621a644531a2e05e7774616ab4d9d85eab1f3b0e255f3102937fccab1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.1.2"
|
||||
version: "12.0.0"
|
||||
hive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -900,26 +908,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11"
|
||||
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
version: "7.2.1"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7"
|
||||
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
version: "3.3.1"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac
|
||||
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.2.2"
|
||||
shared_preferences_android:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1344,18 +1352,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: "82f6787d5df55907aa01e49bd9644f4ed1cc82af7a8257dd9947815959d2e755"
|
||||
sha256: c1ab9b81090705c6069197d9fdc1625e587b52b8d70cdde2339d177ad0dbb98e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
version: "4.4.1"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: ddc167c6676f57c8b367d19fcbee267d6dc6adf81bd6c3cb87981d30746e0a6d
|
||||
sha256: b0cd33dd7d3dd8e5f664e11a19e17ba12c352647269921a3b568406b001f1dff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.10.1"
|
||||
version: "3.12.0"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1368,10 +1376,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: "485af05f2c5f83c7f78c20e236b170ad02df7153b299ae9917345be43871d29f"
|
||||
sha256: "30b9af6bdd457b44c08748b9190d23208b5165357cc2eb57914fee1366c42974"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.8.0"
|
||||
version: "3.9.1"
|
||||
win32:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
@ -1422,4 +1430,4 @@ packages:
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.1.0 <4.0.0"
|
||||
flutter: ">=3.13.6"
|
||||
flutter: ">=3.13.8"
|
||||
|
25
pubspec.yaml
25
pubspec.yaml
@ -1,11 +1,11 @@
|
||||
name: hacki
|
||||
description: A Hacker News reader.
|
||||
version: 1.9.3+124
|
||||
version: 2.0.0+125
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
flutter: "3.13.6"
|
||||
flutter: "3.13.8"
|
||||
|
||||
dependencies:
|
||||
adaptive_theme: ^3.2.0
|
||||
@ -14,8 +14,8 @@ dependencies:
|
||||
cached_network_image: ^3.2.3
|
||||
clipboard: ^0.1.3
|
||||
collection: ^1.17.1
|
||||
connectivity_plus: ^4.0.0
|
||||
device_info_plus: ^9.0.0
|
||||
connectivity_plus: ^5.0.1
|
||||
device_info_plus: ^9.1.0
|
||||
dio: ^5.0.3
|
||||
equatable: ^2.0.5
|
||||
fast_gbk: ^1.0.0
|
||||
@ -30,15 +30,16 @@ dependencies:
|
||||
flutter_email_sender: ^6.0.1
|
||||
flutter_fadein: ^2.0.0
|
||||
flutter_feather_icons: 2.0.0+1
|
||||
flutter_inappwebview: ^5.7.2+3
|
||||
flutter_local_notifications: ^15.1.0+1
|
||||
flutter_inappwebview: ^5.8.0
|
||||
flutter_local_notifications: ^16.1.0
|
||||
flutter_material_color_picker: ^1.2.0
|
||||
flutter_secure_storage: ^9.0.0
|
||||
flutter_siri_suggestions: ^2.1.0
|
||||
flutter_slidable: ^3.0.0
|
||||
font_awesome_flutter: ^10.3.0
|
||||
gbk_codec: ^0.4.0
|
||||
get_it: ^7.2.0
|
||||
go_router: ^11.1.2
|
||||
go_router: ^12.0.0
|
||||
hive: ^2.2.3
|
||||
html: ^0.15.1
|
||||
html_unescape: ^2.0.0
|
||||
@ -66,10 +67,10 @@ dependencies:
|
||||
rxdart: ^0.27.7
|
||||
scrollable_positioned_list: ^0.3.5
|
||||
sembast: ^3.4.0+6
|
||||
share_plus: ^7.0.0
|
||||
shared_preferences: ^2.0.17
|
||||
shared_preferences_android: ^2.0.15
|
||||
shared_preferences_foundation: ^2.1.3
|
||||
share_plus: ^7.2.1
|
||||
shared_preferences: ^2.2.2
|
||||
shared_preferences_android: ^2.2.1
|
||||
shared_preferences_foundation: ^2.3.4
|
||||
shimmer: ^3.0.0
|
||||
synced_shared_preferences:
|
||||
path: components/synced_shared_preferences
|
||||
@ -77,7 +78,7 @@ dependencies:
|
||||
url_launcher: ^6.1.9
|
||||
visibility_detector: ^0.4.0+2
|
||||
wakelock: ^0.6.2
|
||||
webview_flutter: ^4.0.2
|
||||
webview_flutter: ^4.4.1
|
||||
workmanager: ^0.5.1
|
||||
|
||||
dependency_overrides:
|
||||
|
Submodule submodules/flutter updated: ead455963c...6c4930c4ac
Reference in New Issue
Block a user