b0-cloud first commit
This commit is contained in:
BIN
b0esche_cloud/assets/fonts/animal-park/animal_park.otf
Normal file
BIN
b0esche_cloud/assets/fonts/animal-park/animal_park.otf
Normal file
Binary file not shown.
Binary file not shown.
2
b0esche_cloud/assets/fonts/pixelated-elegance/info.txt
Normal file
2
b0esche_cloud/assets/fonts/pixelated-elegance/info.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
license: Public Domain
|
||||
link: https://www.fontspace.com/pixelated-elegance-font-f126145
|
||||
BIN
b0esche_cloud/assets/fonts/renoire-demo/renoire_demo.otf
Normal file
BIN
b0esche_cloud/assets/fonts/renoire-demo/renoire_demo.otf
Normal file
Binary file not shown.
Binary file not shown.
2
b0esche_cloud/assets/fonts/sparky-stones/info.txt
Normal file
2
b0esche_cloud/assets/fonts/sparky-stones/info.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
license: Freeware
|
||||
link: https://www.fontspace.com/sparky-stones-font-f88004
|
||||
@@ -0,0 +1,5 @@
|
||||
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 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
||||
43
b0esche_cloud/ios/Podfile
Normal file
43
b0esche_cloud/ios/Podfile
Normal file
@@ -0,0 +1,43 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
||||
42
b0esche_cloud/lib/blocs/auth/auth_bloc.dart
Normal file
42
b0esche_cloud/lib/blocs/auth/auth_bloc.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'auth_event.dart';
|
||||
import 'auth_state.dart';
|
||||
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
AuthBloc() : super(AuthInitial()) {
|
||||
on<LoginRequested>(_onLoginRequested);
|
||||
on<LogoutRequested>(_onLogoutRequested);
|
||||
on<CheckAuthRequested>(_onCheckAuthRequested);
|
||||
}
|
||||
|
||||
void _onLoginRequested(LoginRequested event, Emitter<AuthState> emit) async {
|
||||
emit(AuthLoading());
|
||||
// Simulate API call to go.b0esche.cloud/auth/login
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (event.email.isNotEmpty && event.password.isNotEmpty) {
|
||||
// Assume JWT received
|
||||
emit(AuthAuthenticated(token: 'fake-jwt', userId: 'user123'));
|
||||
} else {
|
||||
emit(const AuthFailure('Invalid credentials'));
|
||||
}
|
||||
}
|
||||
|
||||
void _onLogoutRequested(
|
||||
LogoutRequested event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
emit(AuthLoading());
|
||||
// Call logout API
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
emit(AuthUnauthenticated());
|
||||
}
|
||||
|
||||
void _onCheckAuthRequested(
|
||||
CheckAuthRequested event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
// Check if token is valid
|
||||
// For now, assume unauthenticated
|
||||
emit(AuthUnauthenticated());
|
||||
}
|
||||
}
|
||||
22
b0esche_cloud/lib/blocs/auth/auth_event.dart
Normal file
22
b0esche_cloud/lib/blocs/auth/auth_event.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class AuthEvent extends Equatable {
|
||||
const AuthEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginRequested extends AuthEvent {
|
||||
final String email;
|
||||
final String password;
|
||||
|
||||
const LoginRequested(this.email, this.password);
|
||||
|
||||
@override
|
||||
List<Object> get props => [email, password];
|
||||
}
|
||||
|
||||
class LogoutRequested extends AuthEvent {}
|
||||
|
||||
class CheckAuthRequested extends AuthEvent {}
|
||||
33
b0esche_cloud/lib/blocs/auth/auth_state.dart
Normal file
33
b0esche_cloud/lib/blocs/auth/auth_state.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class AuthState extends Equatable {
|
||||
const AuthState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class AuthInitial extends AuthState {}
|
||||
|
||||
class AuthLoading extends AuthState {}
|
||||
|
||||
class AuthAuthenticated extends AuthState {
|
||||
final String token;
|
||||
final String userId;
|
||||
|
||||
const AuthAuthenticated({required this.token, required this.userId});
|
||||
|
||||
@override
|
||||
List<Object> get props => [token, userId];
|
||||
}
|
||||
|
||||
class AuthUnauthenticated extends AuthState {}
|
||||
|
||||
class AuthFailure extends AuthState {
|
||||
final String error;
|
||||
|
||||
const AuthFailure(this.error);
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'document_viewer_event.dart';
|
||||
import 'document_viewer_state.dart';
|
||||
|
||||
class DocumentViewerBloc
|
||||
extends Bloc<DocumentViewerEvent, DocumentViewerState> {
|
||||
DocumentViewerBloc() : super(ViewerInitial()) {
|
||||
on<OpenDocument>(_onOpenDocument);
|
||||
on<CloseDocument>(_onCloseDocument);
|
||||
on<ReloadDocument>(_onReloadDocument);
|
||||
}
|
||||
|
||||
void _onOpenDocument(
|
||||
OpenDocument event,
|
||||
Emitter<DocumentViewerState> emit,
|
||||
) async {
|
||||
emit(ViewerLoading());
|
||||
// Simulate requesting viewer session from backend
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
// Mock: assume PDF viewer
|
||||
final viewUrl = 'https://storage.b0esche.cloud/view/${event.fileId}';
|
||||
final canEdit = false; // Based on permissions
|
||||
final fileName = 'document.pdf';
|
||||
emit(ViewerReady(viewUrl: viewUrl, canEdit: canEdit, fileName: fileName));
|
||||
}
|
||||
|
||||
void _onCloseDocument(
|
||||
CloseDocument event,
|
||||
Emitter<DocumentViewerState> emit,
|
||||
) {
|
||||
emit(ViewerInitial());
|
||||
}
|
||||
|
||||
void _onReloadDocument(
|
||||
ReloadDocument event,
|
||||
Emitter<DocumentViewerState> emit,
|
||||
) {
|
||||
// Reload current document
|
||||
final currentState = state;
|
||||
if (currentState is ViewerReady) {
|
||||
emit(ViewerLoading());
|
||||
// Simulate reload
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
emit(currentState);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class DocumentViewerEvent extends Equatable {
|
||||
const DocumentViewerEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class OpenDocument extends DocumentViewerEvent {
|
||||
final String fileId;
|
||||
final String orgId;
|
||||
|
||||
const OpenDocument({required this.fileId, required this.orgId});
|
||||
|
||||
@override
|
||||
List<Object> get props => [fileId, orgId];
|
||||
}
|
||||
|
||||
class CloseDocument extends DocumentViewerEvent {}
|
||||
|
||||
class ReloadDocument extends DocumentViewerEvent {}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class DocumentViewerState extends Equatable {
|
||||
const DocumentViewerState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class ViewerInitial extends DocumentViewerState {}
|
||||
|
||||
class ViewerLoading extends DocumentViewerState {}
|
||||
|
||||
class ViewerReady extends DocumentViewerState {
|
||||
final String viewUrl;
|
||||
final bool canEdit;
|
||||
final String fileName;
|
||||
|
||||
const ViewerReady({
|
||||
required this.viewUrl,
|
||||
required this.canEdit,
|
||||
required this.fileName,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [viewUrl, canEdit, fileName];
|
||||
}
|
||||
|
||||
class ViewerError extends DocumentViewerState {
|
||||
final String error;
|
||||
|
||||
const ViewerError(this.error);
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
85
b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart
Normal file
85
b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'file_browser_event.dart';
|
||||
import 'file_browser_state.dart';
|
||||
import '../../repositories/file_repository.dart';
|
||||
|
||||
class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
||||
final FileRepository _fileRepository;
|
||||
String _currentOrgId = '';
|
||||
String _currentPath = '/';
|
||||
|
||||
FileBrowserBloc(this._fileRepository) : super(DirectoryInitial()) {
|
||||
on<LoadDirectory>(_onLoadDirectory);
|
||||
on<NavigateToFolder>(_onNavigateToFolder);
|
||||
on<RefreshDirectory>(_onRefreshDirectory);
|
||||
on<ApplySort>(_onApplySort);
|
||||
on<ApplyFilter>(_onApplyFilter);
|
||||
on<ResetFileBrowser>(_onResetFileBrowser);
|
||||
}
|
||||
|
||||
void _onLoadDirectory(
|
||||
LoadDirectory event,
|
||||
Emitter<FileBrowserState> emit,
|
||||
) async {
|
||||
emit(DirectoryLoading());
|
||||
_currentOrgId = event.orgId;
|
||||
_currentPath = event.path;
|
||||
try {
|
||||
final files = await _fileRepository.getFiles(event.orgId, event.path);
|
||||
final breadcrumbs = _generateBreadcrumbs(event.path);
|
||||
if (files.isEmpty) {
|
||||
emit(DirectoryEmpty());
|
||||
} else {
|
||||
emit(DirectoryLoaded(files: files, breadcrumbs: breadcrumbs));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(DirectoryError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onNavigateToFolder(
|
||||
NavigateToFolder event,
|
||||
Emitter<FileBrowserState> emit,
|
||||
) async {
|
||||
// Assume event.folderId is the path
|
||||
add(LoadDirectory(orgId: _currentOrgId, path: event.folderId));
|
||||
}
|
||||
|
||||
void _onRefreshDirectory(
|
||||
RefreshDirectory event,
|
||||
Emitter<FileBrowserState> emit,
|
||||
) async {
|
||||
add(LoadDirectory(orgId: _currentOrgId, path: _currentPath));
|
||||
}
|
||||
|
||||
void _onApplySort(ApplySort event, Emitter<FileBrowserState> emit) {
|
||||
// Implement sorting
|
||||
// For now, just refresh
|
||||
add(RefreshDirectory());
|
||||
}
|
||||
|
||||
void _onApplyFilter(ApplyFilter event, Emitter<FileBrowserState> emit) {
|
||||
// Implement filtering
|
||||
add(RefreshDirectory());
|
||||
}
|
||||
|
||||
void _onResetFileBrowser(
|
||||
ResetFileBrowser event,
|
||||
Emitter<FileBrowserState> emit,
|
||||
) {
|
||||
emit(DirectoryInitial());
|
||||
_currentOrgId = '';
|
||||
_currentPath = '/';
|
||||
}
|
||||
|
||||
List<Breadcrumb> _generateBreadcrumbs(String path) {
|
||||
final parts = path.split('/').where((p) => p.isNotEmpty).toList();
|
||||
final breadcrumbs = <Breadcrumb>[];
|
||||
String currentPath = '';
|
||||
for (final part in parts) {
|
||||
currentPath += '/$part';
|
||||
breadcrumbs.add(Breadcrumb(name: part, path: currentPath));
|
||||
}
|
||||
return breadcrumbs;
|
||||
}
|
||||
}
|
||||
49
b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart
Normal file
49
b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class FileBrowserEvent extends Equatable {
|
||||
const FileBrowserEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadDirectory extends FileBrowserEvent {
|
||||
final String orgId;
|
||||
final String path;
|
||||
|
||||
const LoadDirectory({required this.orgId, required this.path});
|
||||
|
||||
@override
|
||||
List<Object> get props => [orgId, path];
|
||||
}
|
||||
|
||||
class NavigateToFolder extends FileBrowserEvent {
|
||||
final String folderId;
|
||||
|
||||
const NavigateToFolder(this.folderId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [folderId];
|
||||
}
|
||||
|
||||
class RefreshDirectory extends FileBrowserEvent {}
|
||||
|
||||
class ApplySort extends FileBrowserEvent {
|
||||
final String sortBy; // name, date, size
|
||||
|
||||
const ApplySort(this.sortBy);
|
||||
|
||||
@override
|
||||
List<Object> get props => [sortBy];
|
||||
}
|
||||
|
||||
class ApplyFilter extends FileBrowserEvent {
|
||||
final String filter;
|
||||
|
||||
const ApplyFilter(this.filter);
|
||||
|
||||
@override
|
||||
List<Object> get props => [filter];
|
||||
}
|
||||
|
||||
class ResetFileBrowser extends FileBrowserEvent {}
|
||||
44
b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart
Normal file
44
b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../models/file_item.dart';
|
||||
|
||||
class Breadcrumb extends Equatable {
|
||||
final String name;
|
||||
final String path;
|
||||
|
||||
const Breadcrumb({required this.name, required this.path});
|
||||
|
||||
@override
|
||||
List<Object> get props => [name, path];
|
||||
}
|
||||
|
||||
abstract class FileBrowserState extends Equatable {
|
||||
const FileBrowserState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class DirectoryInitial extends FileBrowserState {}
|
||||
|
||||
class DirectoryLoading extends FileBrowserState {}
|
||||
|
||||
class DirectoryLoaded extends FileBrowserState {
|
||||
final List<FileItem> files;
|
||||
final List<Breadcrumb> breadcrumbs;
|
||||
|
||||
const DirectoryLoaded({required this.files, required this.breadcrumbs});
|
||||
|
||||
@override
|
||||
List<Object> get props => [files, breadcrumbs];
|
||||
}
|
||||
|
||||
class DirectoryEmpty extends FileBrowserState {}
|
||||
|
||||
class DirectoryError extends FileBrowserState {
|
||||
final String error;
|
||||
|
||||
const DirectoryError(this.error);
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
61
b0esche_cloud/lib/blocs/organization/organization_bloc.dart
Normal file
61
b0esche_cloud/lib/blocs/organization/organization_bloc.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'organization_event.dart';
|
||||
import 'organization_state.dart';
|
||||
import '../permission/permission_bloc.dart';
|
||||
import '../permission/permission_event.dart';
|
||||
import '../file_browser/file_browser_bloc.dart';
|
||||
import '../file_browser/file_browser_event.dart';
|
||||
import '../upload/upload_bloc.dart';
|
||||
import '../upload/upload_event.dart';
|
||||
|
||||
class OrganizationBloc extends Bloc<OrganizationEvent, OrganizationState> {
|
||||
final PermissionBloc permissionBloc;
|
||||
final FileBrowserBloc fileBrowserBloc;
|
||||
final UploadBloc uploadBloc;
|
||||
|
||||
OrganizationBloc(this.permissionBloc, this.fileBrowserBloc, this.uploadBloc)
|
||||
: super(OrganizationInitial()) {
|
||||
on<LoadOrganizations>(_onLoadOrganizations);
|
||||
on<SelectOrganization>(_onSelectOrganization);
|
||||
}
|
||||
|
||||
void _onLoadOrganizations(
|
||||
LoadOrganizations event,
|
||||
Emitter<OrganizationState> emit,
|
||||
) async {
|
||||
emit(OrganizationLoading());
|
||||
// Simulate loading orgs from API
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
final orgs = [
|
||||
const Organization(id: 'org1', name: 'Personal', role: 'admin'),
|
||||
const Organization(id: 'org2', name: 'Company Inc', role: 'edit'),
|
||||
];
|
||||
emit(OrganizationLoaded(organizations: orgs));
|
||||
}
|
||||
|
||||
void _onSelectOrganization(
|
||||
SelectOrganization event,
|
||||
Emitter<OrganizationState> emit,
|
||||
) {
|
||||
final currentState = state;
|
||||
if (currentState is OrganizationLoaded) {
|
||||
final selected = currentState.organizations.firstWhere(
|
||||
(org) => org.id == event.orgId,
|
||||
orElse: () => currentState.selectedOrg!,
|
||||
);
|
||||
emit(
|
||||
OrganizationLoaded(
|
||||
organizations: currentState.organizations,
|
||||
selectedOrg: selected,
|
||||
),
|
||||
);
|
||||
// Reset all dependent blocs
|
||||
permissionBloc.add(PermissionsReset());
|
||||
fileBrowserBloc.add(ResetFileBrowser());
|
||||
uploadBloc.add(ResetUploads());
|
||||
// Load permissions for the selected org
|
||||
permissionBloc.add(LoadPermissions(event.orgId));
|
||||
}
|
||||
}
|
||||
}
|
||||
19
b0esche_cloud/lib/blocs/organization/organization_event.dart
Normal file
19
b0esche_cloud/lib/blocs/organization/organization_event.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class OrganizationEvent extends Equatable {
|
||||
const OrganizationEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadOrganizations extends OrganizationEvent {}
|
||||
|
||||
class SelectOrganization extends OrganizationEvent {
|
||||
final String orgId;
|
||||
|
||||
const SelectOrganization(this.orgId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [orgId];
|
||||
}
|
||||
46
b0esche_cloud/lib/blocs/organization/organization_state.dart
Normal file
46
b0esche_cloud/lib/blocs/organization/organization_state.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class Organization extends Equatable {
|
||||
final String id;
|
||||
final String name;
|
||||
final String role; // view, edit, admin
|
||||
|
||||
const Organization({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.role,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, name, role];
|
||||
}
|
||||
|
||||
abstract class OrganizationState extends Equatable {
|
||||
const OrganizationState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class OrganizationInitial extends OrganizationState {}
|
||||
|
||||
class OrganizationLoading extends OrganizationState {}
|
||||
|
||||
class OrganizationLoaded extends OrganizationState {
|
||||
final List<Organization> organizations;
|
||||
final Organization? selectedOrg;
|
||||
|
||||
const OrganizationLoaded({required this.organizations, this.selectedOrg});
|
||||
|
||||
@override
|
||||
List<Object> get props => [organizations, selectedOrg ?? ''];
|
||||
}
|
||||
|
||||
class OrganizationError extends OrganizationState {
|
||||
final String error;
|
||||
|
||||
const OrganizationError(this.error);
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
36
b0esche_cloud/lib/blocs/permission/permission_bloc.dart
Normal file
36
b0esche_cloud/lib/blocs/permission/permission_bloc.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'permission_event.dart';
|
||||
import 'permission_state.dart';
|
||||
|
||||
class PermissionBloc extends Bloc<PermissionEvent, PermissionState> {
|
||||
PermissionBloc() : super(PermissionInitial()) {
|
||||
on<LoadPermissions>(_onLoadPermissions);
|
||||
on<PermissionsReset>(_onPermissionsReset);
|
||||
}
|
||||
|
||||
void _onLoadPermissions(
|
||||
LoadPermissions event,
|
||||
Emitter<PermissionState> emit,
|
||||
) async {
|
||||
emit(PermissionLoading());
|
||||
// Simulate loading permissions from backend for orgId
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
// Mock capabilities based on orgId
|
||||
final capabilities = Capabilities(
|
||||
canRead: true,
|
||||
canWrite: event.orgId == 'org1', // Only admin for personal
|
||||
canShare: event.orgId == 'org1',
|
||||
canAdmin: event.orgId == 'org1',
|
||||
canAnnotate: true,
|
||||
canEdit: true,
|
||||
);
|
||||
emit(PermissionLoaded(capabilities));
|
||||
}
|
||||
|
||||
void _onPermissionsReset(
|
||||
PermissionsReset event,
|
||||
Emitter<PermissionState> emit,
|
||||
) {
|
||||
emit(PermissionInitial());
|
||||
}
|
||||
}
|
||||
19
b0esche_cloud/lib/blocs/permission/permission_event.dart
Normal file
19
b0esche_cloud/lib/blocs/permission/permission_event.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class PermissionEvent extends Equatable {
|
||||
const PermissionEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadPermissions extends PermissionEvent {
|
||||
final String orgId;
|
||||
|
||||
const LoadPermissions(this.orgId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [orgId];
|
||||
}
|
||||
|
||||
class PermissionsReset extends PermissionEvent {}
|
||||
58
b0esche_cloud/lib/blocs/permission/permission_state.dart
Normal file
58
b0esche_cloud/lib/blocs/permission/permission_state.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class Capabilities extends Equatable {
|
||||
final bool canRead;
|
||||
final bool canWrite;
|
||||
final bool canShare;
|
||||
final bool canAdmin;
|
||||
final bool canAnnotate;
|
||||
final bool canEdit;
|
||||
|
||||
const Capabilities({
|
||||
required this.canRead,
|
||||
required this.canWrite,
|
||||
required this.canShare,
|
||||
required this.canAdmin,
|
||||
required this.canAnnotate,
|
||||
required this.canEdit,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
canRead,
|
||||
canWrite,
|
||||
canShare,
|
||||
canAdmin,
|
||||
canAnnotate,
|
||||
canEdit,
|
||||
];
|
||||
}
|
||||
|
||||
abstract class PermissionState extends Equatable {
|
||||
const PermissionState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class PermissionInitial extends PermissionState {}
|
||||
|
||||
class PermissionLoading extends PermissionState {}
|
||||
|
||||
class PermissionLoaded extends PermissionState {
|
||||
final Capabilities capabilities;
|
||||
|
||||
const PermissionLoaded(this.capabilities);
|
||||
|
||||
@override
|
||||
List<Object> get props => [capabilities];
|
||||
}
|
||||
|
||||
class PermissionDenied extends PermissionState {
|
||||
final String error;
|
||||
|
||||
const PermissionDenied(this.error);
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
53
b0esche_cloud/lib/blocs/session/session_bloc.dart
Normal file
53
b0esche_cloud/lib/blocs/session/session_bloc.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'session_event.dart';
|
||||
import 'session_state.dart';
|
||||
|
||||
class SessionBloc extends Bloc<SessionEvent, SessionState> {
|
||||
Timer? _expiryTimer;
|
||||
|
||||
SessionBloc() : super(SessionInitial()) {
|
||||
on<SessionStarted>(_onSessionStarted);
|
||||
on<SessionExpired>(_onSessionExpired);
|
||||
on<SessionRefreshed>(_onSessionRefreshed);
|
||||
on<SessionEnded>(_onSessionEnded);
|
||||
}
|
||||
|
||||
void _onSessionStarted(SessionStarted event, Emitter<SessionState> emit) {
|
||||
final expiresAt = DateTime.now().add(
|
||||
const Duration(hours: 1),
|
||||
); // Fake expiry
|
||||
emit(SessionActive(token: event.token, expiresAt: expiresAt));
|
||||
_startExpiryTimer(expiresAt);
|
||||
}
|
||||
|
||||
void _onSessionExpired(SessionExpired event, Emitter<SessionState> emit) {
|
||||
_expiryTimer?.cancel();
|
||||
emit(SessionExpiredState());
|
||||
}
|
||||
|
||||
void _onSessionRefreshed(SessionRefreshed event, Emitter<SessionState> emit) {
|
||||
final expiresAt = DateTime.now().add(const Duration(hours: 1));
|
||||
emit(SessionActive(token: event.newToken, expiresAt: expiresAt));
|
||||
_startExpiryTimer(expiresAt);
|
||||
}
|
||||
|
||||
void _onSessionEnded(SessionEnded event, Emitter<SessionState> emit) {
|
||||
_expiryTimer?.cancel();
|
||||
emit(SessionInitial());
|
||||
}
|
||||
|
||||
void _startExpiryTimer(DateTime expiresAt) {
|
||||
_expiryTimer?.cancel();
|
||||
final duration = expiresAt.difference(DateTime.now());
|
||||
_expiryTimer = Timer(duration, () {
|
||||
add(SessionExpired());
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_expiryTimer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
30
b0esche_cloud/lib/blocs/session/session_event.dart
Normal file
30
b0esche_cloud/lib/blocs/session/session_event.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class SessionEvent extends Equatable {
|
||||
const SessionEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class SessionStarted extends SessionEvent {
|
||||
final String token;
|
||||
|
||||
const SessionStarted(this.token);
|
||||
|
||||
@override
|
||||
List<Object> get props => [token];
|
||||
}
|
||||
|
||||
class SessionExpired extends SessionEvent {}
|
||||
|
||||
class SessionRefreshed extends SessionEvent {
|
||||
final String newToken;
|
||||
|
||||
const SessionRefreshed(this.newToken);
|
||||
|
||||
@override
|
||||
List<Object> get props => [newToken];
|
||||
}
|
||||
|
||||
class SessionEnded extends SessionEvent {}
|
||||
31
b0esche_cloud/lib/blocs/session/session_state.dart
Normal file
31
b0esche_cloud/lib/blocs/session/session_state.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class SessionState extends Equatable {
|
||||
const SessionState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class SessionInitial extends SessionState {}
|
||||
|
||||
class SessionActive extends SessionState {
|
||||
final String token;
|
||||
final DateTime expiresAt;
|
||||
|
||||
const SessionActive({required this.token, required this.expiresAt});
|
||||
|
||||
@override
|
||||
List<Object> get props => [token, expiresAt];
|
||||
}
|
||||
|
||||
class SessionExpiredState extends SessionState {}
|
||||
|
||||
class SessionError extends SessionState {
|
||||
final String error;
|
||||
|
||||
const SessionError(this.error);
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
26
b0esche_cloud/lib/blocs/shared_drive/shared_drive_event.dart
Normal file
26
b0esche_cloud/lib/blocs/shared_drive/shared_drive_event.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class SharedDriveEvent extends Equatable {
|
||||
const SharedDriveEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadSharedDrives extends SharedDriveEvent {
|
||||
final String orgId;
|
||||
|
||||
const LoadSharedDrives(this.orgId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [orgId];
|
||||
}
|
||||
|
||||
class OpenSharedDrive extends SharedDriveEvent {
|
||||
final String driveId;
|
||||
|
||||
const OpenSharedDrive(this.driveId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [driveId];
|
||||
}
|
||||
120
b0esche_cloud/lib/blocs/upload/upload_bloc.dart
Normal file
120
b0esche_cloud/lib/blocs/upload/upload_bloc.dart
Normal file
@@ -0,0 +1,120 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'upload_event.dart';
|
||||
import 'upload_state.dart';
|
||||
import '../../repositories/file_repository.dart';
|
||||
|
||||
class UploadBloc extends Bloc<UploadEvent, UploadState> {
|
||||
final FileRepository _fileRepository;
|
||||
|
||||
UploadBloc(this._fileRepository) : super(UploadInitial()) {
|
||||
on<StartUpload>(_onStartUpload);
|
||||
on<UploadProgress>(_onUploadProgress);
|
||||
on<UploadPaused>(_onUploadPaused);
|
||||
on<UploadCompleted>(_onUploadCompleted);
|
||||
on<UploadFailed>(_onUploadFailed);
|
||||
on<CancelUpload>(_onCancelUpload);
|
||||
on<ResetUploads>(_onResetUploads);
|
||||
}
|
||||
|
||||
void _onStartUpload(StartUpload event, Emitter<UploadState> emit) async {
|
||||
final uploads = event.files
|
||||
.map((file) => UploadItem(fileName: file.name))
|
||||
.toList();
|
||||
emit(UploadInProgress(uploads));
|
||||
|
||||
for (final file in event.files) {
|
||||
try {
|
||||
// Simulate upload
|
||||
await _fileRepository.uploadFile(event.orgId, file);
|
||||
add(UploadCompleted(file));
|
||||
} catch (e) {
|
||||
add(UploadFailed(fileName: file.name, error: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onUploadProgress(UploadProgress event, Emitter<UploadState> emit) {
|
||||
final currentState = state;
|
||||
if (currentState is UploadInProgress) {
|
||||
final updatedUploads = currentState.uploads.map((upload) {
|
||||
if (upload.fileName == event.fileName) {
|
||||
return UploadItem(
|
||||
fileName: upload.fileName,
|
||||
progress: event.progress,
|
||||
isPaused: upload.isPaused,
|
||||
isCompleted: upload.isCompleted,
|
||||
error: upload.error,
|
||||
);
|
||||
}
|
||||
return upload;
|
||||
}).toList();
|
||||
emit(UploadInProgress(updatedUploads));
|
||||
}
|
||||
}
|
||||
|
||||
void _onUploadPaused(UploadPaused event, Emitter<UploadState> emit) {
|
||||
final currentState = state;
|
||||
if (currentState is UploadInProgress) {
|
||||
final updatedUploads = currentState.uploads.map((upload) {
|
||||
if (upload.fileName == event.fileName) {
|
||||
return UploadItem(
|
||||
fileName: upload.fileName,
|
||||
progress: upload.progress,
|
||||
isPaused: true,
|
||||
isCompleted: upload.isCompleted,
|
||||
error: upload.error,
|
||||
);
|
||||
}
|
||||
return upload;
|
||||
}).toList();
|
||||
emit(UploadInProgress(updatedUploads));
|
||||
}
|
||||
}
|
||||
|
||||
void _onUploadCompleted(UploadCompleted event, Emitter<UploadState> emit) {
|
||||
final currentState = state;
|
||||
if (currentState is UploadInProgress) {
|
||||
final updatedUploads = currentState.uploads.map((upload) {
|
||||
if (upload.fileName == event.file.name) {
|
||||
return UploadItem(
|
||||
fileName: upload.fileName,
|
||||
progress: 1.0,
|
||||
isPaused: false,
|
||||
isCompleted: true,
|
||||
error: null,
|
||||
);
|
||||
}
|
||||
return upload;
|
||||
}).toList();
|
||||
emit(UploadInProgress(updatedUploads));
|
||||
}
|
||||
}
|
||||
|
||||
void _onUploadFailed(UploadFailed event, Emitter<UploadState> emit) {
|
||||
final currentState = state;
|
||||
if (currentState is UploadInProgress) {
|
||||
final updatedUploads = currentState.uploads.map((upload) {
|
||||
if (upload.fileName == event.fileName) {
|
||||
return UploadItem(
|
||||
fileName: upload.fileName,
|
||||
progress: upload.progress,
|
||||
isPaused: upload.isPaused,
|
||||
isCompleted: upload.isCompleted,
|
||||
error: event.error,
|
||||
);
|
||||
}
|
||||
return upload;
|
||||
}).toList();
|
||||
emit(UploadInProgress(updatedUploads));
|
||||
}
|
||||
}
|
||||
|
||||
void _onCancelUpload(CancelUpload event, Emitter<UploadState> emit) {
|
||||
// Reset to initial
|
||||
emit(UploadInitial());
|
||||
}
|
||||
|
||||
void _onResetUploads(ResetUploads event, Emitter<UploadState> emit) {
|
||||
emit(UploadInitial());
|
||||
}
|
||||
}
|
||||
73
b0esche_cloud/lib/blocs/upload/upload_event.dart
Normal file
73
b0esche_cloud/lib/blocs/upload/upload_event.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../models/file_item.dart';
|
||||
|
||||
abstract class UploadEvent extends Equatable {
|
||||
const UploadEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class StartUpload extends UploadEvent {
|
||||
final List<FileItem> files;
|
||||
final String targetPath;
|
||||
final String orgId;
|
||||
|
||||
const StartUpload({
|
||||
required this.files,
|
||||
required this.targetPath,
|
||||
required this.orgId,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [files, targetPath, orgId];
|
||||
}
|
||||
|
||||
class UploadProgress extends UploadEvent {
|
||||
final String fileName;
|
||||
final double progress;
|
||||
|
||||
const UploadProgress({required this.fileName, required this.progress});
|
||||
|
||||
@override
|
||||
List<Object> get props => [fileName, progress];
|
||||
}
|
||||
|
||||
class UploadPaused extends UploadEvent {
|
||||
final String fileName;
|
||||
|
||||
const UploadPaused(this.fileName);
|
||||
|
||||
@override
|
||||
List<Object> get props => [fileName];
|
||||
}
|
||||
|
||||
class UploadCompleted extends UploadEvent {
|
||||
final FileItem file;
|
||||
|
||||
const UploadCompleted(this.file);
|
||||
|
||||
@override
|
||||
List<Object> get props => [file];
|
||||
}
|
||||
|
||||
class UploadFailed extends UploadEvent {
|
||||
final String fileName;
|
||||
final String error;
|
||||
|
||||
const UploadFailed({required this.fileName, required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [fileName, error];
|
||||
}
|
||||
|
||||
class CancelUpload extends UploadEvent {
|
||||
final String fileName;
|
||||
|
||||
const CancelUpload(this.fileName);
|
||||
|
||||
@override
|
||||
List<Object> get props => [fileName];
|
||||
}
|
||||
|
||||
class ResetUploads extends UploadEvent {}
|
||||
47
b0esche_cloud/lib/blocs/upload/upload_state.dart
Normal file
47
b0esche_cloud/lib/blocs/upload/upload_state.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class UploadItem extends Equatable {
|
||||
final String fileName;
|
||||
final double progress;
|
||||
final bool isPaused;
|
||||
final bool isCompleted;
|
||||
final String? error;
|
||||
|
||||
const UploadItem({
|
||||
required this.fileName,
|
||||
this.progress = 0.0,
|
||||
this.isPaused = false,
|
||||
this.isCompleted = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [fileName, progress, isPaused, isCompleted, error];
|
||||
}
|
||||
|
||||
abstract class UploadState extends Equatable {
|
||||
const UploadState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class UploadInitial extends UploadState {}
|
||||
|
||||
class UploadInProgress extends UploadState {
|
||||
final List<UploadItem> uploads;
|
||||
|
||||
const UploadInProgress(this.uploads);
|
||||
|
||||
@override
|
||||
List<Object> get props => [uploads];
|
||||
}
|
||||
|
||||
class UploadError extends UploadState {
|
||||
final String error;
|
||||
|
||||
const UploadError(this.error);
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
27
b0esche_cloud/lib/injection.dart
Normal file
27
b0esche_cloud/lib/injection.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'repositories/auth_repository.dart';
|
||||
import 'repositories/file_repository.dart';
|
||||
import 'repositories/mock_auth_repository.dart';
|
||||
import 'repositories/mock_file_repository.dart';
|
||||
import 'services/auth_service.dart';
|
||||
import 'services/file_service.dart';
|
||||
import 'viewmodels/login_view_model.dart';
|
||||
import 'viewmodels/file_explorer_view_model.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
void configureDependencies() {
|
||||
// Register repositories
|
||||
getIt.registerSingleton<AuthRepository>(MockAuthRepository());
|
||||
getIt.registerSingleton<FileRepository>(MockFileRepository());
|
||||
|
||||
// Register services
|
||||
getIt.registerSingleton<AuthService>(AuthService(getIt<AuthRepository>()));
|
||||
getIt.registerSingleton<FileService>(FileService(getIt<FileRepository>()));
|
||||
|
||||
// Register viewmodels
|
||||
getIt.registerSingleton<LoginViewModel>(LoginViewModel(getIt<AuthService>()));
|
||||
getIt.registerSingleton<FileExplorerViewModel>(
|
||||
FileExplorerViewModel(getIt<FileService>()),
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,210 @@
|
||||
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/session/session_bloc.dart';
|
||||
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 'repositories/mock_file_repository.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(
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (context, state) => const HomePage()),
|
||||
GoRoute(path: '/login', builder: (context, state) => const LoginForm()),
|
||||
GoRoute(
|
||||
path: '/viewer/:fileId',
|
||||
builder: (context, state) =>
|
||||
DocumentViewer(fileId: state.pathParameters['fileId']!),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
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());
|
||||
@@ -9,12 +215,26 @@ class MainApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Text('Hello World!'),
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<AuthBloc>(create: (_) => AuthBloc()),
|
||||
BlocProvider<SessionBloc>(create: (_) => SessionBloc()),
|
||||
BlocProvider<OrganizationBloc>(
|
||||
create: (context) => OrganizationBloc(
|
||||
context.read<PermissionBloc>(),
|
||||
context.read<FileBrowserBloc>(),
|
||||
context.read<UploadBloc>(),
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocProvider<PermissionBloc>(create: (_) => PermissionBloc()),
|
||||
BlocProvider<FileBrowserBloc>(
|
||||
create: (_) => FileBrowserBloc(MockFileRepository()),
|
||||
),
|
||||
BlocProvider<UploadBloc>(
|
||||
create: (_) => UploadBloc(MockFileRepository()),
|
||||
),
|
||||
],
|
||||
child: MaterialApp.router(routerConfig: _router, theme: appTheme),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
38
b0esche_cloud/lib/models/file_item.dart
Normal file
38
b0esche_cloud/lib/models/file_item.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
enum FileType { folder, file }
|
||||
|
||||
class FileItem extends Equatable {
|
||||
final String name;
|
||||
final String path;
|
||||
final FileType type;
|
||||
final int size; // in bytes, 0 for folders
|
||||
final DateTime lastModified;
|
||||
|
||||
const FileItem({
|
||||
required this.name,
|
||||
required this.path,
|
||||
required this.type,
|
||||
this.size = 0,
|
||||
required this.lastModified,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, path, type, size, lastModified];
|
||||
|
||||
FileItem copyWith({
|
||||
String? name,
|
||||
String? path,
|
||||
FileType? type,
|
||||
int? size,
|
||||
DateTime? lastModified,
|
||||
}) {
|
||||
return FileItem(
|
||||
name: name ?? this.name,
|
||||
path: path ?? this.path,
|
||||
type: type ?? this.type,
|
||||
size: size ?? this.size,
|
||||
lastModified: lastModified ?? this.lastModified,
|
||||
);
|
||||
}
|
||||
}
|
||||
15
b0esche_cloud/lib/models/user.dart
Normal file
15
b0esche_cloud/lib/models/user.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class User extends Equatable {
|
||||
final String email;
|
||||
final String? name;
|
||||
|
||||
const User({required this.email, this.name});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [email, name];
|
||||
|
||||
User copyWith({String? email, String? name}) {
|
||||
return User(email: email ?? this.email, name: name ?? this.name);
|
||||
}
|
||||
}
|
||||
90
b0esche_cloud/lib/pages/document_viewer.dart
Normal file
90
b0esche_cloud/lib/pages/document_viewer.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../blocs/document_viewer/document_viewer_bloc.dart';
|
||||
import '../blocs/document_viewer/document_viewer_event.dart';
|
||||
import '../blocs/document_viewer/document_viewer_state.dart';
|
||||
|
||||
class DocumentViewer extends StatefulWidget {
|
||||
final String fileId;
|
||||
|
||||
const DocumentViewer({super.key, required this.fileId});
|
||||
|
||||
@override
|
||||
State<DocumentViewer> createState() => _DocumentViewerState();
|
||||
}
|
||||
|
||||
class _DocumentViewerState extends State<DocumentViewer> {
|
||||
late DocumentViewerBloc _viewerBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_viewerBloc = DocumentViewerBloc();
|
||||
_viewerBloc.add(
|
||||
OpenDocument(fileId: widget.fileId, orgId: 'org1'),
|
||||
); // Assume org1
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _viewerBloc,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: BlocBuilder<DocumentViewerBloc, DocumentViewerState>(
|
||||
builder: (context, state) {
|
||||
if (state is ViewerReady) {
|
||||
return Text(state.fileName);
|
||||
}
|
||||
return const Text('Document Viewer');
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () {
|
||||
_viewerBloc.add(ReloadDocument());
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_viewerBloc.add(CloseDocument());
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: BlocBuilder<DocumentViewerBloc, DocumentViewerState>(
|
||||
builder: (context, state) {
|
||||
if (state is ViewerLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (state is ViewerError) {
|
||||
return Center(child: Text('Error: ${state.error}'));
|
||||
}
|
||||
if (state is ViewerReady) {
|
||||
return Container(
|
||||
color: Colors.grey,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Document Viewer Placeholder\n(Backend URL: ${state.viewUrl})',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const Center(child: Text('No document loaded'));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_viewerBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
249
b0esche_cloud/lib/pages/file_explorer.dart
Normal file
249
b0esche_cloud/lib/pages/file_explorer.dart
Normal file
@@ -0,0 +1,249 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:file_picker/file_picker.dart' hide FileType;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../blocs/file_browser/file_browser_bloc.dart';
|
||||
import '../blocs/file_browser/file_browser_event.dart';
|
||||
import '../blocs/file_browser/file_browser_state.dart';
|
||||
import '../blocs/permission/permission_bloc.dart';
|
||||
import '../blocs/permission/permission_state.dart';
|
||||
import '../blocs/upload/upload_bloc.dart';
|
||||
import '../blocs/upload/upload_event.dart';
|
||||
import '../models/file_item.dart';
|
||||
|
||||
class FileExplorer extends StatefulWidget {
|
||||
const FileExplorer({super.key});
|
||||
|
||||
@override
|
||||
State<FileExplorer> createState() => _FileExplorerState();
|
||||
}
|
||||
|
||||
class _FileExplorerState extends State<FileExplorer> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Assume org1 for now
|
||||
context.read<FileBrowserBloc>().add(
|
||||
LoadDirectory(orgId: 'org1', path: '/'),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<FileBrowserBloc, FileBrowserState>(
|
||||
builder: (context, state) {
|
||||
if (state is DirectoryLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (state is DirectoryError) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'Error: ${state.error}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state is DirectoryEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Drive',
|
||||
style: TextStyle(fontSize: 24, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Back button
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
onPressed: () {
|
||||
context.read<FileBrowserBloc>().add(
|
||||
LoadDirectory(orgId: 'org1', path: '/'),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Text(
|
||||
'Empty Folder',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BlocBuilder<PermissionBloc, PermissionState>(
|
||||
builder: (context, permState) {
|
||||
if (permState is PermissionLoaded &&
|
||||
permState.capabilities.canWrite) {
|
||||
return ElevatedButton(
|
||||
onPressed: () async {
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
final files = result.files
|
||||
.map(
|
||||
(file) => FileItem(
|
||||
name: file.name,
|
||||
path: '/${file.name}',
|
||||
type: FileType.file,
|
||||
size: file.size,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
context.read<UploadBloc>().add(
|
||||
StartUpload(
|
||||
files: files,
|
||||
targetPath: '/',
|
||||
orgId: 'org1',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Upload File'),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state is DirectoryLoaded) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Drive',
|
||||
style: TextStyle(fontSize: 24, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Breadcrumbs and back button
|
||||
Visibility(
|
||||
visible: state.breadcrumbs.isNotEmpty,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<FileBrowserBloc>().add(
|
||||
LoadDirectory(orgId: 'org1', path: '/'),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: state.breadcrumbs.map((breadcrumb) {
|
||||
return TextButton(
|
||||
onPressed: () {
|
||||
context.read<FileBrowserBloc>().add(
|
||||
NavigateToFolder(breadcrumb.path),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'${breadcrumb.name}/',
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
BlocBuilder<PermissionBloc, PermissionState>(
|
||||
builder: (context, permState) {
|
||||
if (permState is PermissionLoaded &&
|
||||
permState.capabilities.canWrite) {
|
||||
return ElevatedButton(
|
||||
onPressed: () async {
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
final files = result.files
|
||||
.map(
|
||||
(file) => FileItem(
|
||||
name: file.name,
|
||||
path: '/${file.name}',
|
||||
type: FileType.file,
|
||||
size: file.size,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
context.read<UploadBloc>().add(
|
||||
StartUpload(
|
||||
files: files,
|
||||
targetPath: '/',
|
||||
orgId: 'org1',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Upload File'),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: state.files.length,
|
||||
itemBuilder: (context, index) {
|
||||
final file = state.files[index];
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
file.type == FileType.folder
|
||||
? Icons.folder
|
||||
: Icons.insert_drive_file,
|
||||
color: Colors.white,
|
||||
),
|
||||
title: Text(
|
||||
file.name,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
subtitle: Text(
|
||||
file.type == FileType.folder
|
||||
? 'Folder'
|
||||
: 'File - ${file.size} bytes',
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
onTap: () {
|
||||
if (file.type == FileType.folder) {
|
||||
context.read<FileBrowserBloc>().add(
|
||||
NavigateToFolder(file.path),
|
||||
);
|
||||
} else {
|
||||
// Open viewer
|
||||
context.go('/viewer/${file.name}');
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
175
b0esche_cloud/lib/pages/home_page.dart
Normal file
175
b0esche_cloud/lib/pages/home_page.dart
Normal file
@@ -0,0 +1,175 @@
|
||||
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 'login_form.dart';
|
||||
import 'file_explorer.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
late String _selectedTab = 'Drive';
|
||||
late AnimationController _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: BlocBuilder<AuthBloc, AuthState>(
|
||||
builder: (context, state) {
|
||||
final isLoggedIn = state is AuthAuthenticated;
|
||||
if (isLoggedIn && !_animationController.isAnimating) {
|
||||
_animationController.forward();
|
||||
} else if (!isLoggedIn) {
|
||||
_animationController.reverse();
|
||||
}
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeInOut,
|
||||
width: isLoggedIn
|
||||
? MediaQuery.of(context).size.width * 0.9
|
||||
: 340,
|
||||
height: isLoggedIn
|
||||
? MediaQuery.of(context).size.height * 0.9
|
||||
: 280,
|
||||
child: ClipRRect(
|
||||
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: isLoggedIn
|
||||
? const FileExplorer()
|
||||
: const LoginForm(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'b0esche.cloud',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PixelatedElegance',
|
||||
fontSize: 42,
|
||||
color: Colors.white,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Colors.white,
|
||||
fontFeatures: [const FontFeature.slashedZero()],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 20,
|
||||
child: BlocBuilder<AuthBloc, AuthState>(
|
||||
builder: (context, state) {
|
||||
final isLoggedIn = state is AuthAuthenticated;
|
||||
if (!isLoggedIn) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return ScaleTransition(
|
||||
scale: Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeOutBack,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildNavButton('Drive', Icons.cloud),
|
||||
const SizedBox(width: 16),
|
||||
_buildNavButton('Mail', Icons.mail),
|
||||
const SizedBox(width: 16),
|
||||
_buildNavButton('Add', Icons.add),
|
||||
const SizedBox(width: 16),
|
||||
_buildNavButton('Profile', Icons.person, isAvatar: true),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedTab = label;
|
||||
});
|
||||
},
|
||||
child: isAvatar
|
||||
? CircleAvatar(
|
||||
backgroundColor: isSelected ? highlightColor : defaultColor,
|
||||
child: Icon(icon, color: Colors.black),
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: isSelected ? highlightColor : defaultColor,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isSelected ? highlightColor : defaultColor,
|
||||
fontSize: 12,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
97
b0esche_cloud/lib/pages/login_form.dart
Normal file
97
b0esche_cloud/lib/pages/login_form.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../blocs/auth/auth_bloc.dart';
|
||||
import '../blocs/auth/auth_event.dart';
|
||||
import '../blocs/auth/auth_state.dart';
|
||||
import '../blocs/session/session_bloc.dart';
|
||||
import '../blocs/session/session_event.dart';
|
||||
|
||||
class LoginForm extends StatefulWidget {
|
||||
const LoginForm({super.key});
|
||||
|
||||
@override
|
||||
State<LoginForm> createState() => _LoginFormState();
|
||||
}
|
||||
|
||||
class _LoginFormState extends State<LoginForm> {
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is AuthFailure) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(state.error)));
|
||||
} else if (state is AuthAuthenticated) {
|
||||
// Start session
|
||||
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),
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
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: const Text('sign in'),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
7
b0esche_cloud/lib/repositories/auth_repository.dart
Normal file
7
b0esche_cloud/lib/repositories/auth_repository.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import '../models/user.dart';
|
||||
|
||||
abstract class AuthRepository {
|
||||
Future<User> login(String email, String password);
|
||||
Future<void> logout();
|
||||
Future<User?> getCurrentUser();
|
||||
}
|
||||
8
b0esche_cloud/lib/repositories/file_repository.dart
Normal file
8
b0esche_cloud/lib/repositories/file_repository.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import '../models/file_item.dart';
|
||||
|
||||
abstract class FileRepository {
|
||||
Future<List<FileItem>> getFiles(String orgId, String path);
|
||||
Future<FileItem?> getFile(String orgId, String path);
|
||||
Future<void> uploadFile(String orgId, FileItem file);
|
||||
Future<void> deleteFile(String orgId, String path);
|
||||
}
|
||||
25
b0esche_cloud/lib/repositories/mock_auth_repository.dart
Normal file
25
b0esche_cloud/lib/repositories/mock_auth_repository.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import '../models/user.dart';
|
||||
import '../repositories/auth_repository.dart';
|
||||
|
||||
class MockAuthRepository implements AuthRepository {
|
||||
@override
|
||||
Future<User> login(String email, String password) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (email.isNotEmpty && password.isNotEmpty) {
|
||||
return User(email: email);
|
||||
} else {
|
||||
throw Exception('Invalid credentials');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout() async {
|
||||
// Mock logout
|
||||
}
|
||||
|
||||
@override
|
||||
Future<User?> getCurrentUser() async {
|
||||
// Mock: return null or a user
|
||||
return null;
|
||||
}
|
||||
}
|
||||
63
b0esche_cloud/lib/repositories/mock_file_repository.dart
Normal file
63
b0esche_cloud/lib/repositories/mock_file_repository.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import '../models/file_item.dart';
|
||||
import '../repositories/file_repository.dart';
|
||||
|
||||
class MockFileRepository implements FileRepository {
|
||||
final List<FileItem> _files = [
|
||||
FileItem(
|
||||
name: 'Documents',
|
||||
path: '/Documents',
|
||||
type: FileType.folder,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
name: 'Images',
|
||||
path: '/Images',
|
||||
type: FileType.folder,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
name: 'report.pdf',
|
||||
path: '/report.pdf',
|
||||
type: FileType.file,
|
||||
size: 1024,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
name: 'code.dart',
|
||||
path: '/code.dart',
|
||||
type: FileType.file,
|
||||
size: 512,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<List<FileItem>> getFiles(String orgId, String path) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (path == '/') {
|
||||
return _files;
|
||||
} else {
|
||||
// For subfolders, return empty for simplicity
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FileItem?> getFile(String orgId, String path) async {
|
||||
return _files.firstWhere(
|
||||
(f) => f.path == path,
|
||||
orElse: () => null as FileItem,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> uploadFile(String orgId, FileItem file) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
_files.add(file);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteFile(String orgId, String path) async {
|
||||
_files.removeWhere((f) => f.path == path);
|
||||
}
|
||||
}
|
||||
26
b0esche_cloud/lib/services/auth_service.dart
Normal file
26
b0esche_cloud/lib/services/auth_service.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import '../models/user.dart';
|
||||
import '../repositories/auth_repository.dart';
|
||||
|
||||
class AuthService {
|
||||
final AuthRepository _authRepository;
|
||||
|
||||
AuthService(this._authRepository);
|
||||
|
||||
Future<User> login(String email, String password) async {
|
||||
if (email.isEmpty || password.isEmpty) {
|
||||
throw Exception('Email and password are required');
|
||||
}
|
||||
if (!email.contains('@')) {
|
||||
throw Exception('Invalid email format');
|
||||
}
|
||||
return await _authRepository.login(email, password);
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
await _authRepository.logout();
|
||||
}
|
||||
|
||||
Future<User?> getCurrentUser() async {
|
||||
return await _authRepository.getCurrentUser();
|
||||
}
|
||||
}
|
||||
36
b0esche_cloud/lib/services/file_service.dart
Normal file
36
b0esche_cloud/lib/services/file_service.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import '../models/file_item.dart';
|
||||
import '../repositories/file_repository.dart';
|
||||
|
||||
class FileService {
|
||||
final FileRepository _fileRepository;
|
||||
|
||||
FileService(this._fileRepository);
|
||||
|
||||
Future<List<FileItem>> getFiles(String orgId, String path) async {
|
||||
if (path.isEmpty) {
|
||||
throw Exception('Path cannot be empty');
|
||||
}
|
||||
return await _fileRepository.getFiles(orgId, path);
|
||||
}
|
||||
|
||||
Future<FileItem?> getFile(String orgId, String path) async {
|
||||
if (path.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return await _fileRepository.getFile(orgId, path);
|
||||
}
|
||||
|
||||
Future<void> uploadFile(String orgId, FileItem file) async {
|
||||
if (file.name.isEmpty) {
|
||||
throw Exception('File name cannot be empty');
|
||||
}
|
||||
await _fileRepository.uploadFile(orgId, file);
|
||||
}
|
||||
|
||||
Future<void> deleteFile(String orgId, String path) async {
|
||||
if (path.isEmpty) {
|
||||
throw Exception('Path cannot be empty');
|
||||
}
|
||||
await _fileRepository.deleteFile(orgId, path);
|
||||
}
|
||||
}
|
||||
56
b0esche_cloud/lib/viewmodels/file_explorer_view_model.dart
Normal file
56
b0esche_cloud/lib/viewmodels/file_explorer_view_model.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/file_item.dart';
|
||||
import '../services/file_service.dart';
|
||||
|
||||
class FileExplorerViewModel extends ChangeNotifier {
|
||||
final FileService _fileService;
|
||||
|
||||
FileExplorerViewModel(this._fileService);
|
||||
|
||||
List<FileItem> _files = [];
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
String _currentPath = '/';
|
||||
|
||||
List<FileItem> get files => _files;
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
String get currentPath => _currentPath;
|
||||
|
||||
Future<void> loadFiles([String? path]) async {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
if (path != null) _currentPath = path;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
_files = await _fileService.getFiles("", _currentPath);
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
_files = [];
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> uploadFile(FileItem file) async {
|
||||
try {
|
||||
await _fileService.uploadFile("", file);
|
||||
await loadFiles(); // Reload files
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteFile(String path) async {
|
||||
try {
|
||||
await _fileService.deleteFile("", path);
|
||||
await loadFiles(); // Reload files
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
65
b0esche_cloud/lib/viewmodels/login_view_model.dart
Normal file
65
b0esche_cloud/lib/viewmodels/login_view_model.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/user.dart';
|
||||
import '../services/auth_service.dart';
|
||||
|
||||
class LoginViewModel extends ChangeNotifier {
|
||||
final AuthService _authService;
|
||||
|
||||
LoginViewModel(this._authService);
|
||||
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
User? _currentUser;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
User? get currentUser => _currentUser;
|
||||
bool get isLoggedIn => _currentUser != null;
|
||||
|
||||
Future<void> login(String email, String password) async {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
_currentUser = await _authService.login(email, password);
|
||||
_error = null;
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
_currentUser = null;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
await _authService.logout();
|
||||
_currentUser = null;
|
||||
_error = null;
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> checkCurrentUser() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
_currentUser = await _authService.getCurrentUser();
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,26 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <irondash_engine_context/irondash_engine_context_plugin.h>
|
||||
#include <super_native_extensions/super_native_extensions_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
|
||||
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
|
||||
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
|
||||
g_autoptr(FlPluginRegistrar) super_native_extensions_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin");
|
||||
super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_drop
|
||||
flutter_secure_storage_linux
|
||||
irondash_engine_context
|
||||
super_native_extensions
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
||||
@@ -5,6 +5,30 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import connectivity_plus
|
||||
import desktop_drop
|
||||
import device_info_plus
|
||||
import file_picker
|
||||
import flutter_secure_storage_macos
|
||||
import irondash_engine_context
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import super_native_extensions
|
||||
import syncfusion_pdfviewer_macos
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
|
||||
SyncfusionFlutterPdfViewerPlugin.register(with: registry.registrar(forPlugin: "SyncfusionFlutterPdfViewerPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
||||
42
b0esche_cloud/macos/Podfile
Normal file
42
b0esche_cloud/macos/Podfile
Normal file
@@ -0,0 +1,42 @@
|
||||
platform :osx, '10.15'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
name: b0esche_cloud
|
||||
description: "A new Flutter project."
|
||||
publish_to: 'none'
|
||||
publish_to: "none"
|
||||
version: 0.1.0
|
||||
|
||||
environment:
|
||||
@@ -10,10 +10,85 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# State Management
|
||||
flutter_bloc: ^8.1.3
|
||||
bloc: ^8.1.2
|
||||
|
||||
# Networking
|
||||
dio: ^5.3.2
|
||||
|
||||
# Routing
|
||||
go_router: ^12.1.1
|
||||
|
||||
# Dependency Injection
|
||||
get_it: ^7.6.4
|
||||
injectable: ^2.3.2
|
||||
|
||||
# Local Storage
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
shared_preferences: ^2.2.2
|
||||
flutter_secure_storage: ^9.0.0
|
||||
|
||||
# Internationalization
|
||||
intl: ^0.19.0
|
||||
|
||||
# Logging
|
||||
logger: ^2.0.2
|
||||
|
||||
# Image Handling
|
||||
cached_network_image: ^3.3.0
|
||||
|
||||
# SVG Support
|
||||
flutter_svg: ^2.0.9
|
||||
|
||||
# Utilities
|
||||
equatable: ^2.0.5
|
||||
path_provider: ^2.1.2
|
||||
connectivity_plus: ^5.0.2
|
||||
provider: ^6.1.1
|
||||
file_picker: ^8.1.2
|
||||
flutter_dropzone: ^4.0.0
|
||||
desktop_drop: ^0.4.4
|
||||
super_context_menu: ^0.8.5
|
||||
infinite_scroll_pagination: ^4.0.0
|
||||
collection: ^1.18.0
|
||||
syncfusion_flutter_pdfviewer: ^24.2.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
# Code Generation
|
||||
build_runner: ^2.4.6
|
||||
json_serializable: ^6.7.1
|
||||
injectable_generator: ^2.4.1
|
||||
hive_generator: ^2.0.1
|
||||
|
||||
# Testing
|
||||
mockito: ^5.4.4
|
||||
bloc_test: ^9.1.5
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
assets:
|
||||
- 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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
@@ -27,12 +28,37 @@
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
|
||||
<!-- Preload fonts -->
|
||||
<link rel="preload" href="assets/fonts/veteran-typewriter/veteran_typewriter.ttf" as="font" type="font/ttf"
|
||||
crossorigin>
|
||||
<link rel="preload" href="assets/fonts/animal-park/animal_park.otf" as="font" type="font/otf" crossorigin>
|
||||
<link rel="preload" href="assets/fonts/renoire-demo/renoire_demo.otf" as="font" type="font/otf" crossorigin>
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'VeteranTypewriter';
|
||||
src: url('assets/fonts/veteran-typewriter/veteran_typewriter.ttf') format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'AnimalPark';
|
||||
src: url('assets/fonts/animal-park/animal_park.otf') format('opentype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'RenoireDemo';
|
||||
src: url('assets/fonts/renoire-demo/renoire_demo.otf') format('opentype');
|
||||
}
|
||||
</style>
|
||||
|
||||
<title>b0esche_cloud</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
@@ -6,6 +6,27 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <irondash_engine_context/irondash_engine_context_plugin_c_api.h>
|
||||
#include <super_native_extensions/super_native_extensions_plugin_c_api.h>
|
||||
#include <syncfusion_pdfviewer_windows/syncfusion_pdfviewer_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
DesktopDropPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
IrondashEngineContextPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi"));
|
||||
SuperNativeExtensionsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi"));
|
||||
SyncfusionPdfviewerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SyncfusionPdfviewerWindowsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus
|
||||
desktop_drop
|
||||
flutter_secure_storage_windows
|
||||
irondash_engine_context
|
||||
super_native_extensions
|
||||
syncfusion_pdfviewer_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Reference in New Issue
Block a user