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 'package:bloc/bloc.dart';
|
||||||
import 'file_browser_event.dart';
|
import 'file_browser_event.dart';
|
||||||
import 'file_browser_state.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> {
|
class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
||||||
final FileRepository _fileRepository;
|
final FileService _fileService;
|
||||||
String _currentOrgId = '';
|
String _currentOrgId = '';
|
||||||
String _currentPath = '/';
|
String _currentPath = '/';
|
||||||
|
List<FileItem> _currentFiles = [];
|
||||||
|
|
||||||
FileBrowserBloc(this._fileRepository) : super(DirectoryInitial()) {
|
FileBrowserBloc(this._fileService) : super(DirectoryInitial()) {
|
||||||
on<LoadDirectory>(_onLoadDirectory);
|
on<LoadDirectory>(_onLoadDirectory);
|
||||||
on<NavigateToFolder>(_onNavigateToFolder);
|
on<NavigateToFolder>(_onNavigateToFolder);
|
||||||
on<RefreshDirectory>(_onRefreshDirectory);
|
on<RefreshDirectory>(_onRefreshDirectory);
|
||||||
on<ApplySort>(_onApplySort);
|
on<ApplySort>(_onApplySort);
|
||||||
on<ApplyFilter>(_onApplyFilter);
|
on<ApplyFilter>(_onApplyFilter);
|
||||||
|
on<CreateFolder>(_onCreateFolder);
|
||||||
on<ResetFileBrowser>(_onResetFileBrowser);
|
on<ResetFileBrowser>(_onResetFileBrowser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,12 +28,20 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
_currentOrgId = event.orgId;
|
_currentOrgId = event.orgId;
|
||||||
_currentPath = event.path;
|
_currentPath = event.path;
|
||||||
try {
|
try {
|
||||||
final files = await _fileRepository.getFiles(event.orgId, event.path);
|
final files = await _fileService.getFiles(event.orgId, event.path);
|
||||||
final breadcrumbs = _generateBreadcrumbs(event.path);
|
final breadcrumbs = _generateBreadcrumbs(event.path);
|
||||||
|
_currentFiles = files;
|
||||||
if (files.isEmpty) {
|
if (files.isEmpty) {
|
||||||
emit(DirectoryEmpty());
|
emit(DirectoryEmpty());
|
||||||
} else {
|
} else {
|
||||||
emit(DirectoryLoaded(files: files, breadcrumbs: breadcrumbs));
|
emit(
|
||||||
|
DirectoryLoaded(
|
||||||
|
files: files,
|
||||||
|
filteredFiles: files,
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
currentPath: event.path,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(DirectoryError(e.toString()));
|
emit(DirectoryError(e.toString()));
|
||||||
@@ -60,7 +71,36 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
|
|
||||||
void _onApplyFilter(ApplyFilter event, Emitter<FileBrowserState> emit) {
|
void _onApplyFilter(ApplyFilter event, Emitter<FileBrowserState> emit) {
|
||||||
// Implement filtering
|
// 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(
|
void _onResetFileBrowser(
|
||||||
|
|||||||
@@ -46,4 +46,19 @@ class ApplyFilter extends FileBrowserEvent {
|
|||||||
List<Object> get props => [filter];
|
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 {}
|
class ResetFileBrowser extends FileBrowserEvent {}
|
||||||
|
|||||||
@@ -24,12 +24,19 @@ class DirectoryLoading extends FileBrowserState {}
|
|||||||
|
|
||||||
class DirectoryLoaded extends FileBrowserState {
|
class DirectoryLoaded extends FileBrowserState {
|
||||||
final List<FileItem> files;
|
final List<FileItem> files;
|
||||||
|
final List<FileItem> filteredFiles;
|
||||||
final List<Breadcrumb> breadcrumbs;
|
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
|
@override
|
||||||
List<Object> get props => [files, breadcrumbs];
|
List<Object> get props => [files, filteredFiles, breadcrumbs, currentPath];
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectoryEmpty extends FileBrowserState {}
|
class DirectoryEmpty extends FileBrowserState {}
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import 'blocs/organization/organization_bloc.dart';
|
|||||||
import 'blocs/permission/permission_bloc.dart';
|
import 'blocs/permission/permission_bloc.dart';
|
||||||
import 'blocs/file_browser/file_browser_bloc.dart';
|
import 'blocs/file_browser/file_browser_bloc.dart';
|
||||||
import 'blocs/upload/upload_bloc.dart';
|
import 'blocs/upload/upload_bloc.dart';
|
||||||
|
import 'services/file_service.dart';
|
||||||
import 'repositories/mock_file_repository.dart';
|
import 'repositories/mock_file_repository.dart';
|
||||||
|
import 'theme/app_theme.dart';
|
||||||
import 'pages/home_page.dart';
|
import 'pages/home_page.dart';
|
||||||
import 'pages/login_form.dart';
|
import 'pages/login_form.dart';
|
||||||
import 'pages/file_explorer.dart';
|
|
||||||
import 'pages/document_viewer.dart';
|
import 'pages/document_viewer.dart';
|
||||||
|
|
||||||
final GoRouter _router = GoRouter(
|
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() {
|
void main() {
|
||||||
runApp(const MainApp());
|
runApp(const MainApp());
|
||||||
}
|
}
|
||||||
@@ -228,13 +48,16 @@ class MainApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
BlocProvider<PermissionBloc>(create: (_) => PermissionBloc()),
|
BlocProvider<PermissionBloc>(create: (_) => PermissionBloc()),
|
||||||
BlocProvider<FileBrowserBloc>(
|
BlocProvider<FileBrowserBloc>(
|
||||||
create: (_) => FileBrowserBloc(MockFileRepository()),
|
create: (_) => FileBrowserBloc(FileService(MockFileRepository())),
|
||||||
),
|
),
|
||||||
BlocProvider<UploadBloc>(
|
BlocProvider<UploadBloc>(
|
||||||
create: (_) => UploadBloc(MockFileRepository()),
|
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 'package:flutter/material.dart';
|
||||||
|
import '../theme/app_theme.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../blocs/document_viewer/document_viewer_bloc.dart';
|
import '../blocs/document_viewer/document_viewer_bloc.dart';
|
||||||
import '../blocs/document_viewer/document_viewer_event.dart';
|
import '../blocs/document_viewer/document_viewer_event.dart';
|
||||||
@@ -65,12 +66,12 @@ class _DocumentViewerState extends State<DocumentViewer> {
|
|||||||
}
|
}
|
||||||
if (state is ViewerReady) {
|
if (state is ViewerReady) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.grey,
|
color: AppTheme.secondaryText,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Document Viewer Placeholder\n(Backend URL: ${state.viewUrl})',
|
'Document Viewer Placeholder\n(Backend URL: ${state.viewUrl})',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(color: Colors.white),
|
style: const TextStyle(color: AppTheme.primaryText),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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_bloc.dart';
|
||||||
import '../blocs/auth/auth_state.dart';
|
import '../blocs/auth/auth_state.dart';
|
||||||
|
import '../theme/app_theme.dart';
|
||||||
import 'login_form.dart';
|
import 'login_form.dart';
|
||||||
import 'file_explorer.dart';
|
import 'file_explorer.dart';
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: AppTheme.primaryBackground,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
@@ -61,18 +61,139 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||||
child: Container(
|
child: Stack(
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
color: Colors.white.withValues(alpha: 0.1),
|
Container(
|
||||||
borderRadius: BorderRadius.circular(16),
|
decoration: AppTheme.glassDecoration,
|
||||||
border: Border.all(
|
|
||||||
color: Colors.white.withValues(alpha: 0.2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: isLoggedIn
|
child: isLoggedIn
|
||||||
? const FileExplorer()
|
? const FileExplorer()
|
||||||
: const LoginForm(),
|
: 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(
|
style: TextStyle(
|
||||||
fontFamily: 'PixelatedElegance',
|
fontFamily: 'PixelatedElegance',
|
||||||
fontSize: 42,
|
fontSize: 42,
|
||||||
color: Colors.white,
|
color: AppTheme.primaryText,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
decorationColor: Colors.white,
|
decorationColor: AppTheme.primaryText,
|
||||||
fontFeatures: [const FontFeature.slashedZero()],
|
fontFeatures: [const FontFeature.slashedZero()],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -136,7 +257,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
Widget _buildNavButton(String label, IconData icon, {bool isAvatar = false}) {
|
Widget _buildNavButton(String label, IconData icon, {bool isAvatar = false}) {
|
||||||
final isSelected = _selectedTab == label;
|
final isSelected = _selectedTab == label;
|
||||||
final highlightColor = Color.fromARGB(255, 100, 200, 255);
|
final highlightColor = Color.fromARGB(255, 100, 200, 255);
|
||||||
final defaultColor = Colors.white70;
|
final defaultColor = AppTheme.secondaryText;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -147,7 +268,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
child: isAvatar
|
child: isAvatar
|
||||||
? CircleAvatar(
|
? CircleAvatar(
|
||||||
backgroundColor: isSelected ? highlightColor : defaultColor,
|
backgroundColor: isSelected ? highlightColor : defaultColor,
|
||||||
child: Icon(icon, color: Colors.black),
|
child: Icon(icon, color: AppTheme.primaryBackground),
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import '../blocs/auth/auth_event.dart';
|
|||||||
import '../blocs/auth/auth_state.dart';
|
import '../blocs/auth/auth_state.dart';
|
||||||
import '../blocs/session/session_bloc.dart';
|
import '../blocs/session/session_bloc.dart';
|
||||||
import '../blocs/session/session_event.dart';
|
import '../blocs/session/session_event.dart';
|
||||||
|
import '../theme/app_theme.dart';
|
||||||
|
import '../theme/modern_glass_button.dart';
|
||||||
|
|
||||||
class LoginForm extends StatefulWidget {
|
class LoginForm extends StatefulWidget {
|
||||||
const LoginForm({super.key});
|
const LoginForm({super.key});
|
||||||
@@ -30,53 +32,81 @@ class _LoginFormState extends State<LoginForm> {
|
|||||||
context.read<SessionBloc>().add(SessionStarted(state.token));
|
context.read<SessionBloc>().add(SessionStarted(state.token));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'sign in',
|
'sign in',
|
||||||
style: TextStyle(fontSize: 24, color: Colors.white),
|
style: TextStyle(fontSize: 24, color: AppTheme.primaryText),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
decoration: const InputDecoration(
|
textInputAction: TextInputAction.next,
|
||||||
labelText: 'email',
|
keyboardType: TextInputType.emailAddress,
|
||||||
labelStyle: TextStyle(color: Colors.white),
|
cursorColor: AppTheme.accentColor,
|
||||||
enabledBorder: OutlineInputBorder(
|
decoration: InputDecoration(
|
||||||
borderSide: BorderSide(color: Colors.white),
|
hintText: 'email',
|
||||||
),
|
hintStyle: TextStyle(color: AppTheme.secondaryText),
|
||||||
focusedBorder: OutlineInputBorder(
|
contentPadding: const EdgeInsets.all(12),
|
||||||
borderSide: BorderSide(color: Colors.white),
|
border: InputBorder.none,
|
||||||
|
prefixIcon: Icon(
|
||||||
|
Icons.email_outlined,
|
||||||
|
color: AppTheme.primaryText,
|
||||||
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: const TextStyle(color: Colors.white),
|
style: const TextStyle(color: AppTheme.primaryText),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
color: AppTheme.accentColor.withValues(alpha: 0.2),
|
||||||
|
height: 1,
|
||||||
|
thickness: 1,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
decoration: const InputDecoration(
|
textInputAction: TextInputAction.done,
|
||||||
labelText: 'password',
|
keyboardType: TextInputType.visiblePassword,
|
||||||
labelStyle: TextStyle(color: Colors.white),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.white),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
style: const TextStyle(color: Colors.white),
|
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),
|
const SizedBox(height: 16),
|
||||||
BlocBuilder<AuthBloc, AuthState>(
|
SizedBox(
|
||||||
|
width: 150,
|
||||||
|
child: BlocBuilder<AuthBloc, AuthState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is AuthLoading) {
|
return ModernGlassButton(
|
||||||
return const CircularProgressIndicator();
|
isLoading: state is AuthLoading,
|
||||||
}
|
|
||||||
return ElevatedButton(
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<AuthBloc>().add(
|
context.read<AuthBloc>().add(
|
||||||
LoginRequested(
|
LoginRequested(
|
||||||
@@ -89,9 +119,11 @@ class _LoginFormState extends State<LoginForm> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ abstract class FileRepository {
|
|||||||
Future<FileItem?> getFile(String orgId, String path);
|
Future<FileItem?> getFile(String orgId, String path);
|
||||||
Future<void> uploadFile(String orgId, FileItem file);
|
Future<void> uploadFile(String orgId, FileItem file);
|
||||||
Future<void> deleteFile(String orgId, String path);
|
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 {
|
Future<void> deleteFile(String orgId, String path) async {
|
||||||
_files.removeWhere((f) => f.path == path);
|
_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);
|
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/
|
- assets/fonts/
|
||||||
|
|
||||||
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
|
- family: PixelatedElegance
|
||||||
fonts:
|
fonts:
|
||||||
- asset: assets/fonts/pixelated-elegance/PixelatedEleganceRegular-ovyAA.ttf
|
- asset: assets/fonts/pixelated-elegance/PixelatedEleganceRegular-ovyAA.ttf
|
||||||
|
|||||||
Reference in New Issue
Block a user