b0-cloud second commit
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
license: Freeware
|
||||
link: https://www.fontspace.com/sparky-stones-font-f88004
|
||||
@@ -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
|
||||
Binary file not shown.
@@ -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(
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
69
b0esche_cloud/lib/theme/README.md
Normal file
69
b0esche_cloud/lib/theme/README.md
Normal 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
|
||||
41
b0esche_cloud/lib/theme/app_theme.dart
Normal file
41
b0esche_cloud/lib/theme/app_theme.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
158
b0esche_cloud/lib/theme/modern_glass_button.dart
Normal file
158
b0esche_cloud/lib/theme/modern_glass_button.dart
Normal 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),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user