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"
|
#include "Generated.xcconfig"
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
#include "Generated.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/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() {
|
void main() {
|
||||||
runApp(const MainApp());
|
runApp(const MainApp());
|
||||||
@@ -9,12 +215,26 @@ class MainApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const MaterialApp(
|
return MultiBlocProvider(
|
||||||
home: Scaffold(
|
providers: [
|
||||||
body: Center(
|
BlocProvider<AuthBloc>(create: (_) => AuthBloc()),
|
||||||
child: Text('Hello World!'),
|
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 "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) {
|
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
|
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
|
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"
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
|
|||||||
@@ -5,6 +5,30 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
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) {
|
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
|
name: b0esche_cloud
|
||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
publish_to: 'none'
|
publish_to: "none"
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@@ -10,10 +10,85 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: 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:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^6.0.0
|
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:
|
flutter:
|
||||||
uses-material-design: true
|
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>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<!--
|
<!--
|
||||||
If you are serving your web app in a path other than the root, change the
|
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">
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- 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>
|
<title>b0esche_cloud</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<script src="flutter_bootstrap.js" async></script>
|
<script src="flutter_bootstrap.js" async></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
||||||
@@ -6,6 +6,27 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#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) {
|
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
|
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
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|||||||
Reference in New Issue
Block a user