첫 커밋: 로컬 프로젝트 업로드

This commit is contained in:
2026-06-10 15:51:34 +09:00
commit 6a8dbeb2e9
1211 changed files with 312864 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
ThemeData buildLightTheme() {
final scheme =
ColorScheme.fromSeed(
seedColor: const Color(0xFF1A1F2C),
brightness: Brightness.light,
).copyWith(
surface: Colors.white,
surfaceContainerLowest: const Color(0xFFF7F8FA),
surfaceContainerLow: const Color(0xFFF3F4F6),
surfaceContainerHighest: const Color(0xFFE5E7EB),
outline: const Color(0xFFD1D5DB),
outlineVariant: const Color(0xFFE5E7EB),
primary: const Color(0xFF1A1F2C),
onPrimary: Colors.white,
onSurface: const Color(0xFF111827),
onSurfaceVariant: const Color(0xFF6B7280),
);
return _buildTheme(scheme);
}
ThemeData buildDarkTheme() {
final scheme =
ColorScheme.fromSeed(
seedColor: const Color(0xFF7DD3FC),
brightness: Brightness.dark,
).copyWith(
surface: const Color(0xFF0F172A),
surfaceContainerLowest: const Color(0xFF020617),
surfaceContainerLow: const Color(0xFF111827),
surfaceContainerHighest: const Color(0xFF1F2937),
outline: const Color(0xFF334155),
outlineVariant: const Color(0xFF1E293B),
primary: const Color(0xFFBAE6FD),
onPrimary: const Color(0xFF082F49),
onSurface: const Color(0xFFF8FAFC),
onSurfaceVariant: const Color(0xFF94A3B8),
);
return _buildTheme(scheme);
}
ThemeData _buildTheme(ColorScheme colorScheme) {
final isDark = colorScheme.brightness == Brightness.dark;
final base = ThemeData(useMaterial3: true, colorScheme: colorScheme);
return base.copyWith(
scaffoldBackgroundColor: colorScheme.surfaceContainerLowest,
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: NoTransitionsBuilder(),
TargetPlatform.iOS: NoTransitionsBuilder(),
TargetPlatform.linux: NoTransitionsBuilder(),
TargetPlatform.macOS: NoTransitionsBuilder(),
TargetPlatform.windows: NoTransitionsBuilder(),
TargetPlatform.fuchsia: NoTransitionsBuilder(),
},
),
appBarTheme: AppBarTheme(
elevation: 0,
centerTitle: false,
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
surfaceTintColor: Colors.transparent,
),
cardTheme: CardThemeData(
color: colorScheme.surface,
elevation: 0,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: colorScheme.outlineVariant),
),
),
dividerTheme: DividerThemeData(
color: colorScheme.outlineVariant,
thickness: 1,
),
drawerTheme: DrawerThemeData(
backgroundColor: colorScheme.surface,
surfaceTintColor: Colors.transparent,
),
dialogTheme: DialogThemeData(
backgroundColor: colorScheme.surface,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: isDark ? colorScheme.surfaceContainerLow : colorScheme.surface,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: colorScheme.outline),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: colorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: colorScheme.primary, width: 1.4),
),
labelStyle: TextStyle(color: colorScheme.onSurfaceVariant),
hintStyle: TextStyle(color: colorScheme.onSurfaceVariant),
prefixIconColor: colorScheme.onSurfaceVariant,
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(50),
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: colorScheme.onSurface,
side: BorderSide(color: colorScheme.outline),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
),
tabBarTheme: TabBarThemeData(
dividerColor: colorScheme.outlineVariant,
labelColor: colorScheme.onSurface,
unselectedLabelColor: colorScheme.onSurfaceVariant,
indicatorColor: colorScheme.primary,
),
);
}
class NoTransitionsBuilder extends PageTransitionsBuilder {
const NoTransitionsBuilder();
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return child;
}
}

View File

@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ThemeController extends ValueNotifier<ThemeMode> {
ThemeController._(this.storageKey) : super(ThemeMode.light);
static const appStorageKey = 'userfront_theme';
static const authStorageKey = 'userfront_auth_theme';
static final ThemeController app = ThemeController._(appStorageKey);
static final ThemeController auth = ThemeController._(authStorageKey);
static final ThemeController instance = app;
final String storageKey;
bool get isDark => value == ThemeMode.dark;
Future<void> restore() async {
final prefs = await SharedPreferences.getInstance();
final stored = prefs.getString(storageKey);
value = stored == 'dark' ? ThemeMode.dark : ThemeMode.light;
}
Future<void> setThemeMode(ThemeMode mode) async {
if (value != mode) {
value = mode;
}
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
storageKey,
mode == ThemeMode.dark ? 'dark' : 'light',
);
}
Future<void> toggle() {
return setThemeMode(isDark ? ThemeMode.light : ThemeMode.dark);
}
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'app_theme.dart';
import 'theme_controller.dart';
class ThemeScope extends InheritedWidget {
const ThemeScope({super.key, required this.controller, required super.child});
final ThemeController controller;
static ThemeController of(BuildContext context) {
final scope = context.dependOnInheritedWidgetOfExactType<ThemeScope>();
return scope?.controller ?? ThemeController.app;
}
@override
bool updateShouldNotify(ThemeScope oldWidget) {
return oldWidget.controller != controller;
}
}
class ScopedTheme extends StatelessWidget {
const ScopedTheme({super.key, required this.controller, required this.child});
final ThemeController controller;
final Widget child;
@override
Widget build(BuildContext context) {
return ThemeScope(
controller: controller,
child: ValueListenableBuilder<ThemeMode>(
valueListenable: controller,
builder: (context, mode, _) {
return Theme(
data: mode == ThemeMode.dark ? buildDarkTheme() : buildLightTheme(),
child: child,
);
},
),
);
}
}