🎉 Initial Commit

This commit is contained in:
2025-04-21 00:43:33 +08:00
commit 8e7baaa380
137 changed files with 6127 additions and 0 deletions

171
lib/pods/config.dart Normal file
View File

@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:shared_preferences/shared_preferences.dart';
const kAtkStoreKey = 'nex_user_atk';
const kRtkStoreKey = 'nex_user_rtk';
const kNetworkServerDefault = 'https://api.sn.solsynth.dev';
const kNetworkServerStoreKey = 'app_server_url';
const kAppbarTransparentStoreKey = 'app_bar_transparent';
const kAppBackgroundStoreKey = 'app_has_background';
const kAppColorSchemeStoreKey = 'app_color_scheme';
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
const kAppExpandPostLink = 'app_expand_post_link';
const kAppExpandChatLink = 'app_expand_chat_link';
const kAppRealmCompactView = 'app_realm_compact_view';
const kAppCustomFonts = 'app_custom_fonts';
const kAppMixedFeed = 'app_mixed_feed';
const kAppAutoTranslate = 'app_auto_translate';
const kAppHideBottomNav = 'app_hide_bottom_nav';
const kAppSoundEffects = 'app_sound_effects';
const kAppAprilFoolFeatures = 'app_april_fool_features';
const kAppWindowSize = 'app_window_size';
const Map<String, FilterQuality> kImageQualityLevel = {
'settingsImageQualityLowest': FilterQuality.none,
'settingsImageQualityLow': FilterQuality.low,
'settingsImageQualityMedium': FilterQuality.medium,
'settingsImageQualityHigh': FilterQuality.high,
};
final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
throw UnimplementedError();
});
final imageQualityProvider = Provider<FilterQuality>((ref) {
final prefs = ref.watch(sharedPreferencesProvider);
return kImageQualityLevel.values.elementAtOrNull(
prefs.getInt('app_image_quality') ?? 3,
) ??
FilterQuality.high;
});
final serverUrlProvider = Provider<String>((ref) {
final prefs = ref.watch(sharedPreferencesProvider);
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
});
class AppSettings {
final bool realmCompactView;
final bool mixedFeed;
final bool autoTranslate;
final bool hideBottomNav;
final bool soundEffects;
final bool aprilFoolFeatures;
AppSettings({
required this.realmCompactView,
required this.mixedFeed,
required this.autoTranslate,
required this.hideBottomNav,
required this.soundEffects,
required this.aprilFoolFeatures,
});
AppSettings copyWith({
bool? realmCompactView,
bool? mixedFeed,
bool? autoTranslate,
bool? hideBottomNav,
bool? soundEffects,
bool? aprilFoolFeatures,
}) {
return AppSettings(
realmCompactView: realmCompactView ?? this.realmCompactView,
mixedFeed: mixedFeed ?? this.mixedFeed,
autoTranslate: autoTranslate ?? this.autoTranslate,
hideBottomNav: hideBottomNav ?? this.hideBottomNav,
soundEffects: soundEffects ?? this.soundEffects,
aprilFoolFeatures: aprilFoolFeatures ?? this.aprilFoolFeatures,
);
}
}
class AppSettingsNotifier extends StateNotifier<AppSettings> {
final SharedPreferences prefs;
AppSettingsNotifier(this.prefs)
: super(
AppSettings(
realmCompactView: prefs.getBool(kAppRealmCompactView) ?? false,
mixedFeed: prefs.getBool(kAppMixedFeed) ?? true,
autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false,
hideBottomNav: prefs.getBool(kAppHideBottomNav) ?? false,
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
),
);
void setRealmCompactView(bool value) {
prefs.setBool(kAppRealmCompactView, value);
state = state.copyWith(realmCompactView: value);
}
void setMixedFeed(bool value) {
prefs.setBool(kAppMixedFeed, value);
state = state.copyWith(mixedFeed: value);
}
void setAutoTranslate(bool value) {
prefs.setBool(kAppAutoTranslate, value);
state = state.copyWith(autoTranslate: value);
}
void setHideBottomNav(bool value) {
prefs.setBool(kAppHideBottomNav, value);
state = state.copyWith(hideBottomNav: value);
}
void setSoundEffects(bool value) {
prefs.setBool(kAppSoundEffects, value);
state = state.copyWith(soundEffects: value);
}
void setAprilFoolFeatures(bool value) {
prefs.setBool(kAppAprilFoolFeatures, value);
state = state.copyWith(aprilFoolFeatures: value);
}
}
final appSettingsProvider =
StateNotifierProvider<AppSettingsNotifier, AppSettings>((ref) {
final prefs = ref.watch(sharedPreferencesProvider);
return AppSettingsNotifier(prefs);
});
final updateInfoProvider =
StateNotifierProvider<UpdateInfoNotifier, (String?, String?)>((ref) {
return UpdateInfoNotifier();
});
class UpdateInfoNotifier extends StateNotifier<(String?, String?)> {
UpdateInfoNotifier() : super((null, null));
void setUpdate(String newVersion, String newChangelog) {
state = (newVersion, newChangelog);
}
}
final drawerCollapsedProvider = StateProvider<bool>((ref) => false);
void calcDrawerSize(
WidgetRef ref,
BuildContext context, {
bool withMediaQuery = false,
}) {
bool newDrawerIsCollapsed;
if (withMediaQuery) {
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 600;
} else {
final rpb = ResponsiveBreakpoints.of(context);
newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
}
final current = ref.read(drawerCollapsedProvider);
if (newDrawerIsCollapsed != current) {
ref.read(drawerCollapsedProvider.notifier).state = newDrawerIsCollapsed;
}
}

145
lib/pods/theme.dart Normal file
View File

@ -0,0 +1,145 @@
import 'package:flutter/material.dart';
import 'package:island/pods/config.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final themeProvider = StateNotifierProvider<ThemeNotifier, ThemeSet?>((ref) {
return ThemeNotifier();
});
class ThemeNotifier extends StateNotifier<ThemeSet?> {
ThemeNotifier() : super(null) {
_loadTheme();
}
Future<void> _loadTheme() async {
final theme = await createAppThemeSet();
state = theme;
}
void reloadTheme({
Color? seedColorOverride,
bool? useMaterial3,
String? customFonts,
}) async {
final theme = await createAppThemeSet(
seedColorOverride: seedColorOverride,
useMaterial3: useMaterial3,
customFonts: customFonts,
);
state = theme;
}
}
const kMaterialYouToggleStoreKey = 'app_theme_material_you';
class ThemeSet {
ThemeData light;
ThemeData dark;
ThemeSet({required this.light, required this.dark});
}
Future<ThemeSet> createAppThemeSet({
Color? seedColorOverride,
bool? useMaterial3,
String? customFonts,
}) async {
return ThemeSet(
light: await createAppTheme(
Brightness.light,
useMaterial3: useMaterial3,
customFonts: customFonts,
),
dark: await createAppTheme(
Brightness.dark,
useMaterial3: useMaterial3,
customFonts: customFonts,
),
);
}
Future<ThemeData> createAppTheme(
Brightness brightness, {
Color? seedColorOverride,
bool? useMaterial3,
String? customFonts,
}) async {
final prefs = await SharedPreferences.getInstance();
final seedColorString = prefs.getInt(kAppColorSchemeStoreKey);
final seedColor =
seedColorString != null ? Color(seedColorString) : Colors.indigo;
final colorScheme = ColorScheme.fromSeed(
seedColor: seedColorOverride ?? seedColor,
brightness: brightness,
);
final hasAppBarTransparent =
prefs.getBool(kAppbarTransparentStoreKey) ?? false;
final useM3 =
useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
final inUseFonts =
(customFonts ?? prefs.getString(kAppCustomFonts))
?.split(',')
.map((ele) => ele.trim())
.toList() ??
['Nunito'];
return ThemeData(
useMaterial3: useM3,
colorScheme: colorScheme,
brightness: brightness,
fontFamily: inUseFonts.firstOrNull,
fontFamilyFallback: inUseFonts.sublist(1),
iconTheme: IconThemeData(
fill: 0,
weight: 400,
opticalSize: 20,
color: colorScheme.onSurface,
),
snackBarTheme: SnackBarThemeData(
behavior: useM3 ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: hasAppBarTransparent ? 0 : null,
backgroundColor:
hasAppBarTransparent ? Colors.transparent : colorScheme.primary,
foregroundColor:
hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary,
),
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
TargetPlatform.linux: ZoomPageTransitionsBuilder(),
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
},
),
progressIndicatorTheme: ProgressIndicatorThemeData(year2023: false),
sliderTheme: SliderThemeData(year2023: false),
);
}
extension HexColor on Color {
/// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#".
static Color fromHex(String hexString) {
final buffer = StringBuffer();
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
buffer.write(hexString.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
}
/// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`).
String toHex({bool leadingHashSign = true}) =>
'${leadingHashSign ? '#' : ''}'
'${alpha.toRadixString(16).padLeft(2, '0')}'
'${red.toRadixString(16).padLeft(2, '0')}'
'${green.toRadixString(16).padLeft(2, '0')}'
'${blue.toRadixString(16).padLeft(2, '0')}';
}