Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/cubits/preference/preference_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class PreferenceState extends Equatable {

bool get isDevModeEnabled => _isOn<DevMode>();

bool get isDynamicColorEnabled => _isOn<DynamicColorPreference>();

bool get isHackerNewsThemeEnabled => _isOn<HackerNewsThemePreference>();

bool get isPreviewImageLeftAligned =>
Expand Down
126 changes: 75 additions & 51 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';

import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:equatable/equatable.dart';
import 'package:feature_discovery/feature_discovery.dart';
import 'package:flutter/foundation.dart';
Expand Down Expand Up @@ -232,6 +233,7 @@ class HackiApp extends StatelessWidget {
previous.font != current.font ||
previous.textScaleFactor != current.textScaleFactor ||
previous.isTrueDarkModeEnabled != current.isTrueDarkModeEnabled ||
previous.isDynamicColorEnabled != current.isDynamicColorEnabled ||
previous.isHackerNewsThemeEnabled !=
current.isHackerNewsThemeEnabled ||
previous.isDevModeEnabled != current.isDevModeEnabled,
Expand Down Expand Up @@ -285,59 +287,81 @@ class HackiApp extends StatelessWidget {
Brightness.dark);
}
}();
final ColorScheme colorScheme = ColorScheme.fromSeed(
brightness: isDarkModeEnabled
? Brightness.dark
: Brightness.light,
seedColor: state.appColor,
dynamicSchemeVariant: DynamicSchemeVariant.fidelity,
);
return FeatureDiscovery(
child: MediaQuery(
data: state.textScaleFactor == 1
? MediaQuery.of(context)
: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(
state.textScaleFactor,
return DynamicColorBuilder(
builder:
(
ColorScheme? lightDynamic,
ColorScheme? darkDynamic,
) {
final ColorScheme fallbackColorScheme =
ColorScheme.fromSeed(
brightness: isDarkModeEnabled
? Brightness.dark
: Brightness.light,
seedColor: state.appColor,
dynamicSchemeVariant:
DynamicSchemeVariant.fidelity,
);
final ColorScheme colorScheme =
state.isDynamicColorEnabled
? ((isDarkModeEnabled
? darkDynamic
: lightDynamic) ??
fallbackColorScheme)
: fallbackColorScheme;
return FeatureDiscovery(
child: MediaQuery(
data: state.textScaleFactor == 1
? MediaQuery.of(context)
: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(
state.textScaleFactor,
),
),
child: MaterialApp.router(
title: 'Hacki',
debugShowCheckedModeBanner: false,
darkTheme: state.isHackerNewsThemeEnabled
? HackerNewsDarkTheme.theme
: null,
theme: state.isHackerNewsThemeEnabled
? HackerNewsTheme.theme
: AppTheme.theme(
colorScheme,
state.font,
isDarkModeEnabled:
isDarkModeEnabled,
isTrueDarkModeEnabled:
state.isTrueDarkModeEnabled,
),
routerConfig: router,
builder: state.isDevModeEnabled
? (
BuildContext context,
Widget? child,
) => Stack(
children: <Widget>[
Positioned.fill(child: child!),
DraggableFloatingButton(
onTap: () {
router.push(
Paths.logs.landing,
);
},
child: Icon(
Icons.bug_report,
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
],
)
: null,
),
),
child: MaterialApp.router(
title: 'Hacki',
debugShowCheckedModeBanner: false,
darkTheme: state.isHackerNewsThemeEnabled
? HackerNewsDarkTheme.theme
: null,
theme: state.isHackerNewsThemeEnabled
? HackerNewsTheme.theme
: AppTheme.theme(
colorScheme,
state.font,
isDarkModeEnabled: isDarkModeEnabled,
isTrueDarkModeEnabled:
state.isTrueDarkModeEnabled,
),
routerConfig: router,
builder: state.isDevModeEnabled
? (BuildContext context, Widget? child) =>
Stack(
children: <Widget>[
Positioned.fill(child: child!),
DraggableFloatingButton(
onTap: () {
router.push(Paths.logs.landing);
},
child: Icon(
Icons.bug_report,
color: Theme.of(
context,
).colorScheme.onPrimaryContainer,
),
),
],
)
: null,
),
),
);
},
);
},
);
Expand Down
64 changes: 64 additions & 0 deletions lib/models/preference.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ abstract final class Preference<T> extends Equatable with SettingsDisplayable {
const WebViewBottomSheetPreference(),
const DividerPlaceholder(label: 'Look And Feel'),
const EyeCandyPreference(),
const DynamicColorPreference(),
const HackerNewsThemePreference(),
const HapticFeedbackPreference(),
const TrueDarkModePreference(),
Expand Down Expand Up @@ -117,6 +118,35 @@ final class DividerPlaceholder extends Preference<void> {

abstract final class BooleanPreference extends Preference<bool> {
const BooleanPreference({required super.val});

String? dependencyErrorMessage(Iterable<Preference<dynamic>> preferences) {
final List<String> unmetDependencies = <String>[];

for (final Preference<dynamic> dependency in dependencies) {
final Preference<dynamic>? concreteDependency = preferences
.singleWhereOrNull(
(Preference<dynamic> pref) => pref.key == dependency.key,
);
if (concreteDependency?.val != dependency.val) {
final String action = dependency.val == true ? 'enable' : 'disable';
unmetDependencies.add('$action ${dependency.title}');
}
}

if (unmetDependencies.isEmpty) {
return null;
}

final String precedingDependencies = unmetDependencies
.sublist(0, unmetDependencies.length - 1)
.join(', ');
final String dependencyList = switch (unmetDependencies.length) {
1 => unmetDependencies.single,
2 => '${unmetDependencies.first} and ${unmetDependencies.last}',
_ => '$precedingDependencies, and ${unmetDependencies.last}',
};
return 'Please $dependencyList first.';
}
}

abstract final class IntPreference extends Preference<int> {
Expand Down Expand Up @@ -248,6 +278,11 @@ final class HackerNewsThemePreference extends BooleanPreference {
return HackerNewsThemePreference(val: val);
}

@override
Set<Preference<dynamic>> get dependencies => <Preference<dynamic>>{
const DynamicColorPreference(val: false),
};

@override
String get key => 'hackerNewsThemePreference';

Expand Down Expand Up @@ -795,6 +830,35 @@ final class TrueDarkModePreference extends BooleanPreference {
String get subtitle => 'real dark.';
}

final class DynamicColorPreference extends BooleanPreference {
const DynamicColorPreference({bool? val})
: super(val: val ?? _dynamicColorPreferenceDefaultValue);

static const bool _dynamicColorPreferenceDefaultValue = false;

@override
DynamicColorPreference copyWith({required bool? val}) {
return DynamicColorPreference(val: val);
}

@override
Set<Preference<dynamic>> get dependencies => <Preference<dynamic>>{
const HackerNewsThemePreference(val: false),
};

@override
String get key => 'dynamicColor';

@override
String get title => 'Dynamic Colors';

@override
String get subtitle => 'wallpaper colors.';

@override
bool get isDisplayable => Platform.isAndroid;
}

final class HapticFeedbackPreference extends BooleanPreference {
const HapticFeedbackPreference({bool? val})
: super(val: val ?? _hapticFeedbackModeDefaultValue);
Expand Down
89 changes: 57 additions & 32 deletions lib/screens/settings/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -298,17 +298,18 @@ class _SettingsViewState extends State<SettingsView>
selected: <bool>{
preferenceState.isOn(preference as BooleanPreference),
},
onSelectionChanged:
preference.dependencies.satisfy(
preferenceState.preferences,
)
? (Set<bool> val) {
HapticFeedbackUtils.light();
context.read<PreferenceCubit>().update(
preference.copyWith(val: val.single),
);
}
: null,
onSelectionChanged: (Set<bool> val) {
HapticFeedbackUtils.light();
if (!preference.dependencies.satisfy(
preferenceState.preferences,
)) {
showDependencyError(preference);
return;
}
context.read<PreferenceCubit>().update(
preference.copyWith(val: val.single),
);
},
),
),
)
Expand All @@ -322,25 +323,26 @@ class _SettingsViewState extends State<SettingsView>
value: preferenceState.isOn(
preference as BooleanPreference,
),
onChanged:
preference.dependencies.satisfy(
preferenceState.preferences,
)
? (bool val) {
HapticFeedbackUtils.light();
onChanged: (bool val) {
HapticFeedbackUtils.light();

context.read<PreferenceCubit>().update(
preference.copyWith(val: val),
);
if (val &&
!preference.dependencies.satisfy(
preferenceState.preferences,
)) {
showDependencyError(preference);
return;
}

if (preference is MarkReadStoriesModePreference &&
val == false) {
context.read<StoriesBloc>().add(
ClearAllReadStories(),
);
}
}
: null,
context.read<PreferenceCubit>().update(
preference.copyWith(val: val),
);

if (preference is MarkReadStoriesModePreference &&
val == false) {
context.read<StoriesBloc>().add(ClearAllReadStories());
}
},
activeThumbColor: Theme.of(context).colorScheme.primary,
),
if (preference is MarkReadStoriesModePreference) ...<Widget>[
Expand Down Expand Up @@ -519,10 +521,26 @@ class _SettingsViewState extends State<SettingsView>
);
}

void showDependencyError(Preference<dynamic> preference) {
if (preference is! BooleanPreference) {
return;
}
final String? message = preference.dependencyErrorMessage(
context.read<PreferenceCubit>().state.preferences,
);
if (message != null) {
context
..removeSnackBar()
..showErrorSnackBar(message);
}
}

void showHackerNewsThemeError() {
context
..removeSnackBar()
..showErrorSnackBar('Please disable Hacker News Theme first.');
showDependencyError(const DynamicColorPreference(val: true));
}

void showDynamicColorError() {
showDependencyError(const HackerNewsThemePreference(val: true));
}

void showFontSettingDialog() {
Expand Down Expand Up @@ -621,10 +639,17 @@ class _SettingsViewState extends State<SettingsView>
}

void showColorPicker() {
if (context.read<PreferenceCubit>().state.isHackerNewsThemeEnabled) {
final PreferenceState preferenceState = context
.read<PreferenceCubit>()
.state;
if (preferenceState.isHackerNewsThemeEnabled) {
showHackerNewsThemeError();
return;
}
if (preferenceState.isDynamicColorEnabled) {
showDynamicColorError();
return;
}
showDialog<void>(
context: context,
builder: (_) {
Expand Down
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
dynamic_color:
dependency: "direct main"
description:
name: dynamic_color
sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c"
url: "https://pub.dev"
source: hosted
version: "1.8.1"
equatable:
dependency: "direct main"
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies:
connectivity_plus: ^7.3.1
device_info_plus: ^13.1.0
dio: ^5.11.0
dynamic_color: ^1.8.1
dio_smart_retry: ^7.0.1
equatable: ^2.1.0
fast_gbk: ^1.0.0
Expand Down