diff --git a/b0esche_cloud/assets/fonts/animal-park/animal_park.otf b/b0esche_cloud/assets/fonts/animal-park/animal_park.otf deleted file mode 100644 index 6857b5e..0000000 Binary files a/b0esche_cloud/assets/fonts/animal-park/animal_park.otf and /dev/null differ diff --git a/b0esche_cloud/assets/fonts/renoire-demo/renoire_demo.otf b/b0esche_cloud/assets/fonts/renoire-demo/renoire_demo.otf deleted file mode 100644 index e4cf80b..0000000 Binary files a/b0esche_cloud/assets/fonts/renoire-demo/renoire_demo.otf and /dev/null differ diff --git a/b0esche_cloud/assets/fonts/sparky-stones/SparkyStonesRegular-BW6ld.ttf b/b0esche_cloud/assets/fonts/sparky-stones/SparkyStonesRegular-BW6ld.ttf deleted file mode 100644 index 68bd2f5..0000000 Binary files a/b0esche_cloud/assets/fonts/sparky-stones/SparkyStonesRegular-BW6ld.ttf and /dev/null differ diff --git a/b0esche_cloud/assets/fonts/sparky-stones/info.txt b/b0esche_cloud/assets/fonts/sparky-stones/info.txt deleted file mode 100644 index f182574..0000000 --- a/b0esche_cloud/assets/fonts/sparky-stones/info.txt +++ /dev/null @@ -1,2 +0,0 @@ -license: Freeware -link: https://www.fontspace.com/sparky-stones-font-f88004 \ No newline at end of file diff --git a/b0esche_cloud/assets/fonts/veteran-typewriter/license1.txt b/b0esche_cloud/assets/fonts/veteran-typewriter/license1.txt deleted file mode 100644 index a60cbd6..0000000 --- a/b0esche_cloud/assets/fonts/veteran-typewriter/license1.txt +++ /dev/null @@ -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 \ No newline at end of file diff --git a/b0esche_cloud/assets/fonts/veteran-typewriter/veteran_typewriter.ttf b/b0esche_cloud/assets/fonts/veteran-typewriter/veteran_typewriter.ttf deleted file mode 100644 index 202e264..0000000 Binary files a/b0esche_cloud/assets/fonts/veteran-typewriter/veteran_typewriter.ttf and /dev/null differ diff --git a/b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart b/b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart index 7d8651a..d826283 100644 --- a/b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart +++ b/b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart @@ -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 { - final FileRepository _fileRepository; + final FileService _fileService; String _currentOrgId = ''; String _currentPath = '/'; + List _currentFiles = []; - FileBrowserBloc(this._fileRepository) : super(DirectoryInitial()) { + FileBrowserBloc(this._fileService) : super(DirectoryInitial()) { on(_onLoadDirectory); on(_onNavigateToFolder); on(_onRefreshDirectory); on(_onApplySort); on(_onApplyFilter); + on(_onCreateFolder); on(_onResetFileBrowser); } @@ -25,12 +28,20 @@ class FileBrowserBloc extends Bloc { _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 { void _onApplyFilter(ApplyFilter event, Emitter 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 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( diff --git a/b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart b/b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart index 544f6cd..2ff6cdb 100644 --- a/b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart +++ b/b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart @@ -46,4 +46,19 @@ class ApplyFilter extends FileBrowserEvent { List 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 get props => [orgId, parentPath, folderName]; +} + class ResetFileBrowser extends FileBrowserEvent {} diff --git a/b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart b/b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart index b0033c2..a977d6d 100644 --- a/b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart +++ b/b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart @@ -24,12 +24,19 @@ class DirectoryLoading extends FileBrowserState {} class DirectoryLoaded extends FileBrowserState { final List files; + final List filteredFiles; final List 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 get props => [files, breadcrumbs]; + List get props => [files, filteredFiles, breadcrumbs, currentPath]; } class DirectoryEmpty extends FileBrowserState {} diff --git a/b0esche_cloud/lib/main.dart b/b0esche_cloud/lib/main.dart index 04e3c96..cbcf1da 100644 --- a/b0esche_cloud/lib/main.dart +++ b/b0esche_cloud/lib/main.dart @@ -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(create: (_) => PermissionBloc()), BlocProvider( - create: (_) => FileBrowserBloc(MockFileRepository()), + create: (_) => FileBrowserBloc(FileService(MockFileRepository())), ), BlocProvider( create: (_) => UploadBloc(MockFileRepository()), ), ], - child: MaterialApp.router(routerConfig: _router, theme: appTheme), + child: MaterialApp.router( + routerConfig: _router, + theme: AppTheme.darkTheme, + ), ); } } diff --git a/b0esche_cloud/lib/pages/document_viewer.dart b/b0esche_cloud/lib/pages/document_viewer.dart index df9d4f4..c087c00 100644 --- a/b0esche_cloud/lib/pages/document_viewer.dart +++ b/b0esche_cloud/lib/pages/document_viewer.dart @@ -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 { } 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), ), ), ); diff --git a/b0esche_cloud/lib/pages/home_page.dart b/b0esche_cloud/lib/pages/home_page.dart index e2f2f07..cc0615a 100644 --- a/b0esche_cloud/lib/pages/home_page.dart +++ b/b0esche_cloud/lib/pages/home_page.dart @@ -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 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 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 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 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 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, diff --git a/b0esche_cloud/lib/pages/login_form.dart b/b0esche_cloud/lib/pages/login_form.dart index 87e3330..ae5f6a4 100644 --- a/b0esche_cloud/lib/pages/login_form.dart +++ b/b0esche_cloud/lib/pages/login_form.dart @@ -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 { context.read().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( - builder: (context, state) { - if (state is AuthLoading) { - return const CircularProgressIndicator(); - } - return ElevatedButton( - onPressed: () { - context.read().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( + builder: (context, state) { + return ModernGlassButton( + isLoading: state is AuthLoading, + onPressed: () { + context.read().add( + LoginRequested( + _emailController.text, + _passwordController.text, + ), + ); + }, + child: const Text('sign in'), ); }, - child: const Text('sign in'), - ); - }, - ), - ], + ), + ), + ], + ), ), ), ); diff --git a/b0esche_cloud/lib/repositories/file_repository.dart b/b0esche_cloud/lib/repositories/file_repository.dart index 0f4d6b0..8a63a0d 100644 --- a/b0esche_cloud/lib/repositories/file_repository.dart +++ b/b0esche_cloud/lib/repositories/file_repository.dart @@ -5,4 +5,5 @@ abstract class FileRepository { Future getFile(String orgId, String path); Future uploadFile(String orgId, FileItem file); Future deleteFile(String orgId, String path); + Future createFolder(String orgId, String parentPath, String folderName); } diff --git a/b0esche_cloud/lib/repositories/mock_file_repository.dart b/b0esche_cloud/lib/repositories/mock_file_repository.dart index d40e614..c787feb 100644 --- a/b0esche_cloud/lib/repositories/mock_file_repository.dart +++ b/b0esche_cloud/lib/repositories/mock_file_repository.dart @@ -60,4 +60,22 @@ class MockFileRepository implements FileRepository { Future deleteFile(String orgId, String path) async { _files.removeWhere((f) => f.path == path); } + + @override + Future 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(), + ), + ); + } } diff --git a/b0esche_cloud/lib/services/file_service.dart b/b0esche_cloud/lib/services/file_service.dart index bff4f12..e309ec2 100644 --- a/b0esche_cloud/lib/services/file_service.dart +++ b/b0esche_cloud/lib/services/file_service.dart @@ -33,4 +33,15 @@ class FileService { } await _fileRepository.deleteFile(orgId, path); } + + Future createFolder( + String orgId, + String parentPath, + String folderName, + ) async { + if (folderName.isEmpty) { + throw Exception('Folder name cannot be empty'); + } + await _fileRepository.createFolder(orgId, parentPath, folderName); + } } diff --git a/b0esche_cloud/lib/theme/README.md b/b0esche_cloud/lib/theme/README.md new file mode 100644 index 0000000..33a33c1 --- /dev/null +++ b/b0esche_cloud/lib/theme/README.md @@ -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 diff --git a/b0esche_cloud/lib/theme/app_theme.dart b/b0esche_cloud/lib/theme/app_theme.dart new file mode 100644 index 0000000..aba890c --- /dev/null +++ b/b0esche_cloud/lib/theme/app_theme.dart @@ -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, + ); +} diff --git a/b0esche_cloud/lib/theme/modern_glass_button.dart b/b0esche_cloud/lib/theme/modern_glass_button.dart new file mode 100644 index 0000000..8266a9d --- /dev/null +++ b/b0esche_cloud/lib/theme/modern_glass_button.dart @@ -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 createState() => _ModernGlassButtonState(); +} + +class _ModernGlassButtonState extends State + 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(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( + 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), + // ], + // ), + // ), + // ), + // ), + ], + ), + ), + ), + ); + } +} diff --git a/b0esche_cloud/pubspec.yaml b/b0esche_cloud/pubspec.yaml index 3675dca..cccebae 100644 --- a/b0esche_cloud/pubspec.yaml +++ b/b0esche_cloud/pubspec.yaml @@ -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