b0-cloud second commit

This commit is contained in:
Leon Bösche
2025-12-16 21:19:01 +01:00
parent 7d372a1aac
commit bd9dc5e485
20 changed files with 602 additions and 284 deletions

View File

@@ -1,2 +0,0 @@
license: Freeware
link: https://www.fontspace.com/sparky-stones-font-f88004

View File

@@ -1,5 +0,0 @@
This font is free to use personal and commercial works too. But you can't sell them direktly.
Please don't make illegal copies of the fonts.
Thanks and regards: Koczman B<>lint

View File

@@ -1,19 +1,22 @@
import 'package:bloc/bloc.dart';
import 'file_browser_event.dart';
import 'file_browser_state.dart';
import '../../repositories/file_repository.dart';
import '../../services/file_service.dart';
import '../../models/file_item.dart';
class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
final FileRepository _fileRepository;
final FileService _fileService;
String _currentOrgId = '';
String _currentPath = '/';
List<FileItem> _currentFiles = [];
FileBrowserBloc(this._fileRepository) : super(DirectoryInitial()) {
FileBrowserBloc(this._fileService) : super(DirectoryInitial()) {
on<LoadDirectory>(_onLoadDirectory);
on<NavigateToFolder>(_onNavigateToFolder);
on<RefreshDirectory>(_onRefreshDirectory);
on<ApplySort>(_onApplySort);
on<ApplyFilter>(_onApplyFilter);
on<CreateFolder>(_onCreateFolder);
on<ResetFileBrowser>(_onResetFileBrowser);
}
@@ -25,12 +28,20 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
_currentOrgId = event.orgId;
_currentPath = event.path;
try {
final files = await _fileRepository.getFiles(event.orgId, event.path);
final files = await _fileService.getFiles(event.orgId, event.path);
final breadcrumbs = _generateBreadcrumbs(event.path);
_currentFiles = files;
if (files.isEmpty) {
emit(DirectoryEmpty());
} else {
emit(DirectoryLoaded(files: files, breadcrumbs: breadcrumbs));
emit(
DirectoryLoaded(
files: files,
filteredFiles: files,
breadcrumbs: breadcrumbs,
currentPath: event.path,
),
);
}
} catch (e) {
emit(DirectoryError(e.toString()));
@@ -60,7 +71,36 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
void _onApplyFilter(ApplyFilter event, Emitter<FileBrowserState> emit) {
// Implement filtering
add(RefreshDirectory());
final filtered = _currentFiles
.where((f) => f.name.toLowerCase().contains(event.filter.toLowerCase()))
.toList();
emit(
DirectoryLoaded(
files: _currentFiles,
filteredFiles: filtered,
breadcrumbs: _generateBreadcrumbs(_currentPath),
currentPath: _currentPath,
),
);
}
void _onCreateFolder(
CreateFolder event,
Emitter<FileBrowserState> emit,
) async {
try {
await _fileService.createFolder(
event.orgId,
event.parentPath,
event.folderName,
);
// Refresh the directory to show the new folder
add(LoadDirectory(orgId: event.orgId, path: event.parentPath));
} catch (e) {
// For now, emit error state or handle appropriately
// Since states don't have error for create, perhaps refresh anyway
add(LoadDirectory(orgId: event.orgId, path: event.parentPath));
}
}
void _onResetFileBrowser(

View File

@@ -46,4 +46,19 @@ class ApplyFilter extends FileBrowserEvent {
List<Object> get props => [filter];
}
class CreateFolder extends FileBrowserEvent {
final String orgId;
final String parentPath;
final String folderName;
const CreateFolder({
required this.orgId,
required this.parentPath,
required this.folderName,
});
@override
List<Object> get props => [orgId, parentPath, folderName];
}
class ResetFileBrowser extends FileBrowserEvent {}

View File

@@ -24,12 +24,19 @@ class DirectoryLoading extends FileBrowserState {}
class DirectoryLoaded extends FileBrowserState {
final List<FileItem> files;
final List<FileItem> filteredFiles;
final List<Breadcrumb> breadcrumbs;
final String currentPath;
const DirectoryLoaded({required this.files, required this.breadcrumbs});
const DirectoryLoaded({
required this.files,
required this.filteredFiles,
required this.breadcrumbs,
required this.currentPath,
});
@override
List<Object> get props => [files, breadcrumbs];
List<Object> get props => [files, filteredFiles, breadcrumbs, currentPath];
}
class DirectoryEmpty extends FileBrowserState {}

View File

@@ -7,10 +7,11 @@ import 'blocs/organization/organization_bloc.dart';
import 'blocs/permission/permission_bloc.dart';
import 'blocs/file_browser/file_browser_bloc.dart';
import 'blocs/upload/upload_bloc.dart';
import 'services/file_service.dart';
import 'repositories/mock_file_repository.dart';
import 'theme/app_theme.dart';
import 'pages/home_page.dart';
import 'pages/login_form.dart';
import 'pages/file_explorer.dart';
import 'pages/document_viewer.dart';
final GoRouter _router = GoRouter(
@@ -25,187 +26,6 @@ final GoRouter _router = GoRouter(
],
);
final ThemeData appTheme = ThemeData(
useMaterial3: true,
colorScheme: const ColorScheme(
brightness: Brightness.dark,
primary: Color(0xFF000000),
onPrimary: Color(0xFFFFFFFF),
primaryContainer: Color(0xFF1C1B1F),
onPrimaryContainer: Color(0xFFE6E1E5),
secondary: Color(0xFF5D5D5D),
onSecondary: Color(0xFFFFFFFF),
secondaryContainer: Color(0xFF2A2A2A),
onSecondaryContainer: Color(0xFFD9D9D9),
tertiary: Color(0xFF7D5260),
onTertiary: Color(0xFFFFFFFF),
tertiaryContainer: Color(0xFF633B48),
onTertiaryContainer: Color(0xFFFFD8E4),
error: Color(0xFFFFB4AB),
onError: Color(0xFF690005),
errorContainer: Color(0xFF93000A),
onErrorContainer: Color(0xFFFFDAD6),
background: Color(0xFF0F0F0F),
onBackground: Color(0xFFE6E1E5),
surface: Color(0xFF0F0F0F),
onSurface: Color(0xFFE6E1E5),
surfaceVariant: Color(0xFF49454F),
onSurfaceVariant: Color(0xFFCAC4D0),
outline: Color(0xFF938F99),
outlineVariant: Color(0xFF49454F),
shadow: Color(0xFF000000),
scrim: Color(0xFF000000),
inverseSurface: Color(0xFFE6E1E5),
onInverseSurface: Color(0xFF322F35),
inversePrimary: Color(0xFF6750A4),
surfaceTint: Color(0xFFD0BCFF),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 57,
fontWeight: FontWeight.w400,
letterSpacing: -0.25,
color: Color(0xFFE6E1E5),
),
displayMedium: TextStyle(
fontSize: 45,
fontWeight: FontWeight.w400,
letterSpacing: 0,
color: Color(0xFFE6E1E5),
),
displaySmall: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w400,
letterSpacing: 0,
color: Color(0xFFE6E1E5),
),
headlineLarge: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w400,
letterSpacing: 0,
color: Color(0xFFE6E1E5),
),
headlineMedium: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w400,
letterSpacing: 0,
color: Color(0xFFE6E1E5),
),
headlineSmall: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w400,
letterSpacing: 0,
color: Color(0xFFE6E1E5),
),
titleLarge: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
letterSpacing: 0,
color: Color(0xFFE6E1E5),
),
titleMedium: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0.15,
color: Color(0xFFE6E1E5),
),
titleSmall: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
letterSpacing: 0.1,
color: Color(0xFFE6E1E5),
),
bodyLarge: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
letterSpacing: 0.5,
color: Color(0xFFE6E1E5),
),
bodyMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: 0.25,
color: Color(0xFFE6E1E5),
),
bodySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.4,
color: Color(0xFFE6E1E5),
),
labelLarge: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
letterSpacing: 0.1,
color: Color(0xFFE6E1E5),
),
labelMedium: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
letterSpacing: 0.5,
color: Color(0xFFE6E1E5),
),
labelSmall: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
letterSpacing: 0.5,
color: Color(0xFFE6E1E5),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF5D5D5D),
foregroundColor: const Color(0xFFFFFFFF),
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: const Color(0xFFD0BCFF),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFFE6E1E5),
side: const BorderSide(color: Color(0xFF938F99)),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
inputDecorationTheme: const InputDecorationTheme(
filled: true,
fillColor: Color(0xFF1C1B1F),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
borderSide: BorderSide(color: Color(0xFF938F99)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
borderSide: BorderSide(color: Color(0xFF938F99)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
borderSide: BorderSide(color: Color(0xFFD0BCFF)),
),
labelStyle: TextStyle(color: Color(0xFFCAC4D0)),
hintStyle: TextStyle(color: Color(0xFF938F99)),
),
cardTheme: CardThemeData(
color: const Color(0xFF1C1B1F),
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF0F0F0F),
foregroundColor: Color(0xFFE6E1E5),
elevation: 0,
),
scaffoldBackgroundColor: const Color(0xFF0F0F0F),
);
void main() {
runApp(const MainApp());
}
@@ -228,13 +48,16 @@ class MainApp extends StatelessWidget {
),
BlocProvider<PermissionBloc>(create: (_) => PermissionBloc()),
BlocProvider<FileBrowserBloc>(
create: (_) => FileBrowserBloc(MockFileRepository()),
create: (_) => FileBrowserBloc(FileService(MockFileRepository())),
),
BlocProvider<UploadBloc>(
create: (_) => UploadBloc(MockFileRepository()),
),
],
child: MaterialApp.router(routerConfig: _router, theme: appTheme),
child: MaterialApp.router(
routerConfig: _router,
theme: AppTheme.darkTheme,
),
);
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../theme/app_theme.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/document_viewer/document_viewer_bloc.dart';
import '../blocs/document_viewer/document_viewer_event.dart';
@@ -65,12 +66,12 @@ class _DocumentViewerState extends State<DocumentViewer> {
}
if (state is ViewerReady) {
return Container(
color: Colors.grey,
color: AppTheme.secondaryText,
child: Center(
child: Text(
'Document Viewer Placeholder\n(Backend URL: ${state.viewUrl})',
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white),
style: const TextStyle(color: AppTheme.primaryText),
),
),
);

View File

@@ -1,9 +1,9 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../blocs/auth/auth_bloc.dart';
import '../blocs/auth/auth_state.dart';
import '../theme/app_theme.dart';
import 'login_form.dart';
import 'file_explorer.dart';
@@ -36,7 +36,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
backgroundColor: AppTheme.primaryBackground,
body: Stack(
children: [
Center(
@@ -61,17 +61,138 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
borderRadius: BorderRadius.circular(16),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.white.withValues(alpha: 0.2),
child: Stack(
children: [
Container(
decoration: AppTheme.glassDecoration,
child: isLoggedIn
? const FileExplorer()
: const LoginForm(),
),
),
child: isLoggedIn
? const FileExplorer()
: const LoginForm(),
// Top-left radial glow - primary accent light
AnimatedPositioned(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
top: isLoggedIn ? -180 : -120,
left: isLoggedIn ? -180 : -120,
child: IgnorePointer(
child: AnimatedContainer(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
width: isLoggedIn ? 550 : 400,
height: isLoggedIn ? 550 : 400,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
AppTheme.accentColor.withValues(
alpha: isLoggedIn ? 0.12 : 0.15,
),
AppTheme.accentColor.withValues(
alpha: 0.04,
),
Colors.transparent,
],
stops: const [0.0, 0.6, 1.0],
),
),
),
),
),
// Bottom-right warm glow - complementary lighting
AnimatedPositioned(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
bottom: isLoggedIn ? -200 : -140,
right: isLoggedIn ? -200 : -140,
child: IgnorePointer(
child: AnimatedContainer(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
width: isLoggedIn ? 530 : 380,
height: isLoggedIn ? 530 : 380,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
Colors.cyan.withValues(
alpha: isLoggedIn ? 0.06 : 0.08,
),
Colors.transparent,
],
),
),
),
),
),
// Top edge subtle highlight
IgnorePointer(
child: Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
height: 60,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.white.withValues(alpha: 0.05),
Colors.transparent,
],
),
),
),
),
),
// Left edge subtle side lighting
IgnorePointer(
child: Positioned(
left: 0,
top: 0,
bottom: 0,
child: Container(
width: 40,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
AppTheme.accentColor.withValues(
alpha: 0.04,
),
Colors.transparent,
],
),
),
),
),
),
// Diagonal shimmer overlay
IgnorePointer(
child: Positioned(
top: -100,
right: -100,
child: Transform.rotate(
angle: 0.785,
child: Container(
width: 600,
height: 100,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.white.withValues(alpha: 0),
Colors.white.withValues(alpha: 0.06),
Colors.white.withValues(alpha: 0),
],
),
),
),
),
),
),
],
),
),
),
@@ -89,9 +210,9 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
style: TextStyle(
fontFamily: 'PixelatedElegance',
fontSize: 42,
color: Colors.white,
color: AppTheme.primaryText,
decoration: TextDecoration.underline,
decorationColor: Colors.white,
decorationColor: AppTheme.primaryText,
fontFeatures: [const FontFeature.slashedZero()],
),
),
@@ -136,7 +257,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
Widget _buildNavButton(String label, IconData icon, {bool isAvatar = false}) {
final isSelected = _selectedTab == label;
final highlightColor = Color.fromARGB(255, 100, 200, 255);
final defaultColor = Colors.white70;
final defaultColor = AppTheme.secondaryText;
return GestureDetector(
onTap: () {
@@ -147,7 +268,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
child: isAvatar
? CircleAvatar(
backgroundColor: isSelected ? highlightColor : defaultColor,
child: Icon(icon, color: Colors.black),
child: Icon(icon, color: AppTheme.primaryBackground),
)
: Column(
mainAxisSize: MainAxisSize.min,

View File

@@ -5,6 +5,8 @@ import '../blocs/auth/auth_event.dart';
import '../blocs/auth/auth_state.dart';
import '../blocs/session/session_bloc.dart';
import '../blocs/session/session_event.dart';
import '../theme/app_theme.dart';
import '../theme/modern_glass_button.dart';
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@@ -30,66 +32,96 @@ class _LoginFormState extends State<LoginForm> {
context.read<SessionBloc>().add(SessionStarted(state.token));
}
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'sign in',
style: TextStyle(fontSize: 24, color: Colors.white),
),
const SizedBox(height: 16),
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'email',
labelStyle: TextStyle(color: Colors.white),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'sign in',
style: TextStyle(fontSize: 24, color: AppTheme.primaryText),
),
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'password',
labelStyle: TextStyle(color: Colors.white),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(
color: AppTheme.primaryBackground.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppTheme.accentColor.withValues(alpha: 0.3),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
),
obscureText: true,
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 16),
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is AuthLoading) {
return const CircularProgressIndicator();
}
return ElevatedButton(
onPressed: () {
context.read<AuthBloc>().add(
LoginRequested(
_emailController.text,
_passwordController.text,
child: Column(
children: [
TextField(
controller: _emailController,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
cursorColor: AppTheme.accentColor,
decoration: InputDecoration(
hintText: 'email',
hintStyle: TextStyle(color: AppTheme.secondaryText),
contentPadding: const EdgeInsets.all(12),
border: InputBorder.none,
prefixIcon: Icon(
Icons.email_outlined,
color: AppTheme.primaryText,
size: 20,
),
),
style: const TextStyle(color: AppTheme.primaryText),
),
Divider(
color: AppTheme.accentColor.withValues(alpha: 0.2),
height: 1,
thickness: 1,
),
TextField(
controller: _passwordController,
textInputAction: TextInputAction.done,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
obscuringCharacter: '',
cursorColor: AppTheme.accentColor,
decoration: InputDecoration(
hintText: 'password',
hintStyle: TextStyle(color: AppTheme.secondaryText),
contentPadding: const EdgeInsets.all(12),
border: InputBorder.none,
prefixIcon: Icon(
Icons.lock_outline_rounded,
color: AppTheme.primaryText,
size: 20,
),
),
style: const TextStyle(color: AppTheme.primaryText),
),
],
),
),
const SizedBox(height: 16),
SizedBox(
width: 150,
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
return ModernGlassButton(
isLoading: state is AuthLoading,
onPressed: () {
context.read<AuthBloc>().add(
LoginRequested(
_emailController.text,
_passwordController.text,
),
);
},
child: const Text('sign in'),
);
},
child: const Text('sign in'),
);
},
),
],
),
),
],
),
),
),
);

View File

@@ -5,4 +5,5 @@ abstract class FileRepository {
Future<FileItem?> getFile(String orgId, String path);
Future<void> uploadFile(String orgId, FileItem file);
Future<void> deleteFile(String orgId, String path);
Future<void> createFolder(String orgId, String parentPath, String folderName);
}

View File

@@ -60,4 +60,22 @@ class MockFileRepository implements FileRepository {
Future<void> deleteFile(String orgId, String path) async {
_files.removeWhere((f) => f.path == path);
}
@override
Future<void> createFolder(
String orgId,
String parentPath,
String folderName,
) async {
await Future.delayed(const Duration(seconds: 1));
final newPath = '$parentPath/$folderName';
_files.add(
FileItem(
name: folderName,
path: newPath,
type: FileType.folder,
lastModified: DateTime.now(),
),
);
}
}

View File

@@ -33,4 +33,15 @@ class FileService {
}
await _fileRepository.deleteFile(orgId, path);
}
Future<void> createFolder(
String orgId,
String parentPath,
String folderName,
) async {
if (folderName.isEmpty) {
throw Exception('Folder name cannot be empty');
}
await _fileRepository.createFolder(orgId, parentPath, folderName);
}
}

View File

@@ -0,0 +1,69 @@
# App Theme Guide
This directory contains the centralized theme configuration for the b0esche.cloud application.
## Files
- `app_theme.dart` - Main theme file containing all color constants and theme definitions
## Using the Theme
Import the theme in any file:
```dart
import '../theme/app_theme.dart';
```
### Color Constants
Use the predefined colors throughout your app:
- `AppTheme.primaryBackground` - Black background (Colors.black)
- `AppTheme.primaryText` - White text (Colors.white)
- `AppTheme.secondaryText` - Light gray text (Colors.white70)
- `AppTheme.accentColor` - Cyan accent color (RGB 100, 200, 255)
- `AppTheme.glassBackground` - White color for glass effect
### Glass Morphism
Use the predefined glass decoration:
```dart
Container(
decoration: AppTheme.glassDecoration,
child: YourWidget(),
)
```
This provides:
- 10% white background opacity
- 10px blur effect
- 16px border radius
- 0.2 opacity white border
### Dimensions
- `AppTheme.glassOpacity` - 0.1 (10%)
- `AppTheme.glassBlur` - 10.0
### Theme Data
The app uses `AppTheme.darkTheme` which is set in `main.dart`:
```dart
MaterialApp.router(
routerConfig: _router,
theme: AppTheme.darkTheme,
)
```
## Best Practices
1. Always use `AppTheme` constants instead of hardcoding colors
2. For new colors, add them to the `AppTheme` class first
3. Update this README when adding new theme properties
4. Use semantic naming (e.g., `primaryText` instead of `lightColor`)
## Files Using Theme
- `lib/pages/home_page.dart` - Main page with navigation buttons
- `lib/pages/login_form.dart` - Login form styling
- `lib/pages/file_explorer.dart` - File explorer interface
- `lib/pages/document_viewer.dart` - Document viewer

View File

@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
class AppTheme {
static const Color primaryBackground = Colors.black;
static const Color accentColor = Color.fromARGB(255, 100, 200, 255);
static const Color secondaryText = Colors.white70;
static const Color primaryText = Colors.white;
static const Color glassBackground = Colors.white;
static const double glassOpacity = 0.1;
static const double glassBlur = 10;
// Glass morphism card styling
static BoxDecoration glassDecoration = BoxDecoration(
color: glassBackground.withValues(alpha: glassOpacity),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withValues(alpha: 0.2)),
);
// Modern elevated button style with glassmorphism and gradient
static ButtonStyle modernButtonStyle = ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
foregroundColor: primaryText,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
);
static ThemeData lightTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.light,
primaryColor: accentColor,
scaffoldBackgroundColor: primaryBackground,
);
static ThemeData darkTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
primaryColor: accentColor,
scaffoldBackgroundColor: primaryBackground,
);
}

View File

@@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'dart:ui';
import 'app_theme.dart';
class ModernGlassButton extends StatefulWidget {
final VoidCallback onPressed;
final Widget child;
final bool isLoading;
const ModernGlassButton({
required this.onPressed,
required this.child,
this.isLoading = false,
super.key,
});
@override
State<ModernGlassButton> createState() => _ModernGlassButtonState();
}
class _ModernGlassButtonState extends State<ModernGlassButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
bool _isHovered = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onHover(bool value) {
setState(() => _isHovered = value);
if (value) {
_controller.forward();
} else {
_controller.reverse();
}
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => _onHover(true),
onExit: (_) => _onHover(false),
child: GestureDetector(
onTap: widget.isLoading ? null : widget.onPressed,
child: ScaleTransition(
scale: Tween<double>(begin: 1.0, end: 1.05).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
),
child: Stack(
children: [
// Shadow layer
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppTheme.accentColor.withValues(alpha: 0.3),
blurRadius: _isHovered ? 24 : 12,
spreadRadius: _isHovered ? 2 : 0,
offset: const Offset(0, 8),
),
BoxShadow(
color: AppTheme.accentColor.withValues(alpha: 0.1),
blurRadius: 20,
spreadRadius: 5,
),
],
),
),
// Glass button with gradient
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 8,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
colors: [
AppTheme.accentColor.withValues(alpha: 0.25),
AppTheme.accentColor.withValues(alpha: 0.15),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
border: Border.all(
color: AppTheme.accentColor.withValues(alpha: 0.4),
width: 1.5,
),
),
child: Center(
child: widget.isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
AppTheme.primaryText,
),
strokeWidth: 2,
),
)
: DefaultTextStyle(
style: const TextStyle(
color: AppTheme.primaryText,
fontWeight: FontWeight.w600,
),
child: widget.child,
),
),
),
),
),
// Light reflection on hover
// if (_isHovered)
// Positioned(
// top: 0,
// left: 5,
// right: 10,
// child: Container(
// height: 4,
// decoration: BoxDecoration(
// borderRadius: const BorderRadius.only(
// topLeft: Radius.circular(12),
// topRight: Radius.circular(12),
// ),
// gradient: LinearGradient(
// colors: [
// Colors.white.withValues(alpha: 0.15),
// Colors.white.withValues(alpha: 0),
// ],
// ),
// ),
// ),
// ),
],
),
),
),
);
}
}

View File

@@ -77,18 +77,6 @@ flutter:
- assets/fonts/
fonts:
- family: VeteranTypewriter
fonts:
- asset: assets/fonts/veteran-typewriter/veteran_typewriter.ttf
- family: AnimalPark
fonts:
- asset: assets/fonts/animal-park/animal_park.otf
- family: RenoireDemo
fonts:
- asset: assets/fonts/renoire-demo/renoire_demo.otf
- family: SparkyStones
fonts:
- asset: assets/fonts/sparky-stones/SparkyStonesRegular-BW6ld.ttf
- family: PixelatedElegance
fonts:
- asset: assets/fonts/pixelated-elegance/PixelatedEleganceRegular-ovyAA.ttf