mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-14 17:25:50 +00:00
Compare commits
27 Commits
Implement-
...
dev
Author | SHA1 | Date | |
---|---|---|---|
5532935a3a | |||
249cbfc242 | |||
8167926620 | |||
559091faa0 | |||
eba351c9be | |||
c112cde634 | |||
65d541d594 | |||
7331c8440b | |||
a409e34643 | |||
7cc59e43df | |||
21f8b2962c | |||
645a07287e | |||
df29aab111 | |||
e55e793081 | |||
83202204b0 | |||
d87739f1fd | |||
5cd083a37b | |||
2b8d987c69 | |||
707cb4791f | |||
03c45ed8d0 | |||
9e0ea4ad6f | |||
9e6b14737f | |||
7c2aed2d58 | |||
bcf62027bc | |||
b001713ce4 | |||
bab3226c73 | |||
fa1eaa570c |
@ -1,2 +1,3 @@
|
|||||||
ENV_NAME=development
|
ENV_NAME=development
|
||||||
BASE_URL=https://syncrow-dev.azurewebsites.net
|
BASE_URL=https://syncrow-dev.azurewebsites.net
|
||||||
|
RTDB_URL=https://syncrow-dev-79446.asia-southeast1.firebasedatabase.app/
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
ENV_NAME=production
|
ENV_NAME=production
|
||||||
BASE_URL=https://syncrow-staging.azurewebsites.net
|
BASE_URL=https://syncrow-staging.azurewebsites.net
|
||||||
|
RTDB_URL=https://syncrow-prod-79446.asia-southeast1.firebasedatabase.app/
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
ENV_NAME=staging
|
ENV_NAME=staging
|
||||||
BASE_URL=https://syncrow-staging.azurewebsites.net
|
BASE_URL=https://syncrow-staging.azurewebsites.net
|
||||||
|
RTDB_URL=https://syncrow-staging-79446.asia-southeast1.firebasedatabase.app/
|
||||||
|
34
.vscode/launch.json
vendored
34
.vscode/launch.json
vendored
@ -1,14 +1,9 @@
|
|||||||
{
|
{
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
"name": "DEVELOPMENT",
|
"name": "DEVELOPMENT",
|
||||||
|
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
|
|
||||||
"args": [
|
"args": [
|
||||||
"-d",
|
"-d",
|
||||||
"chrome",
|
"chrome",
|
||||||
@ -16,19 +11,14 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main_dev.dart",
|
"lib/main_dev.dart",
|
||||||
"--web-experimental-hot-reload",
|
"--web-experimental-hot-reload"
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
|
},
|
||||||
},{
|
{
|
||||||
|
|
||||||
"name": "STAGING",
|
"name": "STAGING",
|
||||||
|
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
|
|
||||||
"args": [
|
"args": [
|
||||||
"-d",
|
"-d",
|
||||||
"chrome",
|
"chrome",
|
||||||
@ -36,19 +26,14 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main_staging.dart",
|
"lib/main_staging.dart",
|
||||||
"--web-experimental-hot-reload",
|
"--web-experimental-hot-reload"
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
|
},
|
||||||
},{
|
{
|
||||||
|
|
||||||
"name": "PRODUCTION",
|
"name": "PRODUCTION",
|
||||||
|
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
|
|
||||||
"args": [
|
"args": [
|
||||||
"-d",
|
"-d",
|
||||||
"chrome",
|
"chrome",
|
||||||
@ -56,12 +41,9 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main.dart",
|
"lib/main.dart",
|
||||||
"--web-experimental-hot-reload",
|
"--web-experimental-hot-reload"
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
|
}
|
||||||
},
|
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:android:2bc36fbe82994a3e0c7e6d","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"test2-8a3d2","configurations":{"android":"1:427332280600:android:2bc36fbe82994a3e0c7e6d","ios":"1:427332280600:ios:14346b200780dc760c7e6d","macos":"1:427332280600:ios:14346b200780dc760c7e6d","web":"1:427332280600:web:ad50516a87a35a1a0c7e6d","windows":"1:427332280600:web:f7a25537ccd5a7bd0c7e6d"}}}}}}
|
{"flutter":{"platforms":{"android":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:android:2bc36fbe82994a3e0c7e6d","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"syncrow-prod-79446","configurations":{"web":"1:255001682464:web:a03e2d6214c13101561245"}}}}}}
|
16
lib/firebase_options.dart
Normal file
16
lib/firebase_options.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||||
|
|
||||||
|
final class DefaultFirebaseOptions extends FirebaseOptions {
|
||||||
|
const DefaultFirebaseOptions({
|
||||||
|
required String databaseUrl,
|
||||||
|
}) : super(
|
||||||
|
apiKey: 'AIzaSyDgq5ywsnFVbbQO-Xz1Z4sR5bBcuiDaS9g',
|
||||||
|
appId: '1:255001682464:web:a03e2d6214c13101561245',
|
||||||
|
messagingSenderId: '255001682464',
|
||||||
|
projectId: 'syncrow-prod-79446',
|
||||||
|
authDomain: 'syncrow-prod-79446.firebaseapp.com',
|
||||||
|
storageBucket: 'syncrow-prod-79446.firebasestorage.app',
|
||||||
|
databaseURL: databaseUrl,
|
||||||
|
measurementId: 'G-1850Q89RMK',
|
||||||
|
);
|
||||||
|
}
|
@ -1,93 +0,0 @@
|
|||||||
// File generated by FlutterFire CLI.
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
|
||||||
import 'package:flutter/foundation.dart'
|
|
||||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
|
||||||
|
|
||||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// import 'firebase_options.dart';
|
|
||||||
/// // ...
|
|
||||||
/// await Firebase.initializeApp(
|
|
||||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
class DefaultFirebaseOptionsDev {
|
|
||||||
static FirebaseOptions get currentPlatform {
|
|
||||||
if (kIsWeb) {
|
|
||||||
return web;
|
|
||||||
}
|
|
||||||
switch (defaultTargetPlatform) {
|
|
||||||
case TargetPlatform.android:
|
|
||||||
return android;
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
return ios;
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
return macos;
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
return windows;
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for linux - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions are not supported for this platform.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const FirebaseOptions web = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyCVEvKsJYzhWDFM-9Od68FE0nPpP933st0',
|
|
||||||
appId: '1:427332280600:web:ad50516a87a35a1a0c7e6d',
|
|
||||||
messagingSenderId: '427332280600',
|
|
||||||
projectId: 'test2-8a3d2',
|
|
||||||
authDomain: 'test2-8a3d2.firebaseapp.com',
|
|
||||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
|
||||||
measurementId: 'G-Z1RTTTV5H9',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions android = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0',
|
|
||||||
appId: '1:427332280600:android:2bc36fbe82994a3e0c7e6d',
|
|
||||||
messagingSenderId: '427332280600',
|
|
||||||
projectId: 'test2-8a3d2',
|
|
||||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions ios = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw',
|
|
||||||
appId: '1:427332280600:ios:14346b200780dc760c7e6d',
|
|
||||||
messagingSenderId: '427332280600',
|
|
||||||
projectId: 'test2-8a3d2',
|
|
||||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
|
||||||
iosBundleId: 'com.example.syncrowWeb',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions macos = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw',
|
|
||||||
appId: '1:427332280600:ios:14346b200780dc760c7e6d',
|
|
||||||
messagingSenderId: '427332280600',
|
|
||||||
projectId: 'test2-8a3d2',
|
|
||||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
|
||||||
iosBundleId: 'com.example.syncrowWeb',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions windows = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyDizKjPC5rdkEjDxwXjM-RU5unB0Ziq3iw',
|
|
||||||
appId: '1:427332280600:web:f7a25537ccd5a7bd0c7e6d',
|
|
||||||
messagingSenderId: '427332280600',
|
|
||||||
projectId: 'test2-8a3d2',
|
|
||||||
authDomain: 'test2-8a3d2.firebaseapp.com',
|
|
||||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
|
||||||
measurementId: 'G-4LFVXEXWKY',
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
// File generated by FlutterFire CLI.
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
|
||||||
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
|
||||||
|
|
||||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// import 'firebase_options.dart';
|
|
||||||
/// // ...
|
|
||||||
/// await Firebase.initializeApp(
|
|
||||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
class DefaultFirebaseOptionsStaging {
|
|
||||||
static FirebaseOptions get currentPlatform {
|
|
||||||
if (kIsWeb) {
|
|
||||||
return web;
|
|
||||||
}
|
|
||||||
switch (defaultTargetPlatform) {
|
|
||||||
case TargetPlatform.android:
|
|
||||||
return android;
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
return ios;
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for macos - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for windows - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for linux - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions are not supported for this platform.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const FirebaseOptions android = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyDP9GpYfLE8gHTj3kZ1hW8fx_FkJqOqSQk',
|
|
||||||
appId: '1:786692570726:android:0ef7079c2b978d4417b7a7',
|
|
||||||
messagingSenderId: '786692570726',
|
|
||||||
projectId: 'syncrow-staging',
|
|
||||||
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'syncrow-staging.appspot.com',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions ios = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyAWlRiuJ75FMlf2_UDdri1voWKvkaSHtRg',
|
|
||||||
appId: '1:786692570726:ios:455a6fcff77e130f17b7a7',
|
|
||||||
messagingSenderId: '786692570726',
|
|
||||||
projectId: 'syncrow-staging',
|
|
||||||
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'syncrow-staging.appspot.com',
|
|
||||||
iosBundleId: 'com.example.syncrow.app',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions web = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyDyGaQ3sZhb4meaY6sGke-YglhdhJ2is8Q',
|
|
||||||
appId: '1:786692570726:web:93c931e6701797b317b7a7',
|
|
||||||
messagingSenderId: '786692570726',
|
|
||||||
projectId: 'syncrow-staging',
|
|
||||||
authDomain: 'syncrow-staging.firebaseapp.com',
|
|
||||||
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
|
|
||||||
storageBucket: 'syncrow-staging.appspot.com',
|
|
||||||
measurementId: 'G-CZ3J3G6LMQ',
|
|
||||||
);
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:syncrow_web/firebase_options_prod.dart';
|
import 'package:syncrow_web/firebase_options.dart';
|
||||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||||
@ -27,7 +27,9 @@ Future<void> main() async {
|
|||||||
await dotenv.load(fileName: '.env.$environment');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptionsStaging.currentPlatform,
|
options: DefaultFirebaseOptions(
|
||||||
|
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -59,7 +61,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:syncrow_web/firebase_options_dev.dart';
|
import 'package:syncrow_web/firebase_options.dart';
|
||||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||||
@ -27,7 +27,9 @@ Future<void> main() async {
|
|||||||
await dotenv.load(fileName: '.env.$environment');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptionsDev.currentPlatform,
|
options: DefaultFirebaseOptions(
|
||||||
|
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -59,7 +61,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:syncrow_web/firebase_options_prod.dart';
|
import 'package:syncrow_web/firebase_options.dart';
|
||||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||||
@ -24,7 +24,9 @@ Future<void> main() async {
|
|||||||
await dotenv.load(fileName: '.env.$environment');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptionsStaging.currentPlatform,
|
options: DefaultFirebaseOptions(
|
||||||
|
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -56,7 +58,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||||
|
|
||||||
|
class RemoteCalendarService implements CalendarSystemService {
|
||||||
|
const RemoteCalendarService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
static const _defaultErrorMessage = 'Failed to load Calendar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CalendarEventsResponse> getCalendarEvents({
|
||||||
|
required String spaceId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: ApiEndpoints.getCalendarEvents,
|
||||||
|
queryParameters: {
|
||||||
|
'spaceId': spaceId,
|
||||||
|
},
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return CalendarEventsResponse.fromJson(
|
||||||
|
json as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return CalendarEventsResponse.fromJson(response as Map<String, dynamic>);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final responseData = e.response?.data;
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
final errorMessage = responseData['error']?['message'] as String? ??
|
||||||
|
responseData['message'] as String? ??
|
||||||
|
_defaultErrorMessage;
|
||||||
|
throw APIException(errorMessage);
|
||||||
|
}
|
||||||
|
throw APIException(_defaultErrorMessage);
|
||||||
|
} catch (e) {
|
||||||
|
throw APIException('$_defaultErrorMessage: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeRemoteCalendarService implements CalendarSystemService {
|
||||||
|
const FakeRemoteCalendarService(this._httpService, {this.useDummy = false});
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
final bool useDummy;
|
||||||
|
static const _defaultErrorMessage = 'Failed to load Calendar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CalendarEventsResponse> getCalendarEvents({
|
||||||
|
required String spaceId,
|
||||||
|
}) async {
|
||||||
|
if (useDummy) {
|
||||||
|
final dummyJson = {
|
||||||
|
'statusCode': 200,
|
||||||
|
'message': 'Successfully fetched all bookings',
|
||||||
|
'data': [
|
||||||
|
{
|
||||||
|
'uuid': 'd4553fa6-a0c9-4f42-81c9-99a13a57bf80',
|
||||||
|
'date': '2025-07-11T10:22:00.626Z',
|
||||||
|
'startTime': '09:00:00',
|
||||||
|
'endTime': '12:00:00',
|
||||||
|
'cost': 10,
|
||||||
|
'user': {
|
||||||
|
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||||
|
'firstName': 'salsabeel',
|
||||||
|
'lastName': 'abuzaid',
|
||||||
|
'email': 'test@test.com',
|
||||||
|
'companyName': null
|
||||||
|
},
|
||||||
|
'space': {
|
||||||
|
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||||
|
'spaceName': '2(1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561',
|
||||||
|
'date': '2025-07-11T10:22:00.626Z',
|
||||||
|
'startTime': '12:00:00',
|
||||||
|
'endTime': '13:00:00',
|
||||||
|
'cost': 10,
|
||||||
|
'user': {
|
||||||
|
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||||
|
'firstName': 'salsabeel',
|
||||||
|
'lastName': 'abuzaid',
|
||||||
|
'email': 'test@test.com',
|
||||||
|
'companyName': null
|
||||||
|
},
|
||||||
|
'space': {
|
||||||
|
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||||
|
'spaceName': '2(1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561',
|
||||||
|
'date': '2025-07-13T10:22:00.626Z',
|
||||||
|
'startTime': '15:30:00',
|
||||||
|
'endTime': '19:00:00',
|
||||||
|
'cost': 20,
|
||||||
|
'user': {
|
||||||
|
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||||
|
'firstName': 'salsabeel',
|
||||||
|
'lastName': 'abuzaid',
|
||||||
|
'email': 'test@test.com',
|
||||||
|
'companyName': null
|
||||||
|
},
|
||||||
|
'space': {
|
||||||
|
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||||
|
'spaceName': '2(1)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'success': true
|
||||||
|
};
|
||||||
|
final response = CalendarEventsResponse.fromJson(dummyJson);
|
||||||
|
|
||||||
|
// Filter events by spaceId
|
||||||
|
final filteredData = response.data.where((event) {
|
||||||
|
return event.space.uuid == spaceId;
|
||||||
|
}).toList();
|
||||||
|
print('Filtering events for spaceId: $spaceId');
|
||||||
|
print('Found ${filteredData.length} matching events');
|
||||||
|
return filteredData.isNotEmpty
|
||||||
|
? CalendarEventsResponse(
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
message: response.message,
|
||||||
|
data: filteredData,
|
||||||
|
success: response.success,
|
||||||
|
)
|
||||||
|
: CalendarEventsResponse(
|
||||||
|
statusCode: 404,
|
||||||
|
message: 'No events found for spaceId: $spaceId',
|
||||||
|
data: [],
|
||||||
|
success: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: ApiEndpoints.getCalendarEvents,
|
||||||
|
queryParameters: {
|
||||||
|
'spaceId': spaceId,
|
||||||
|
},
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return CalendarEventsResponse.fromJson(
|
||||||
|
json as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return CalendarEventsResponse.fromJson(response as Map<String, dynamic>);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final responseData = e.response?.data;
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
final errorMessage = responseData['error']?['message'] as String? ??
|
||||||
|
responseData['message'] as String? ??
|
||||||
|
_defaultErrorMessage;
|
||||||
|
throw APIException(errorMessage);
|
||||||
|
}
|
||||||
|
throw APIException(_defaultErrorMessage);
|
||||||
|
} catch (e) {
|
||||||
|
throw APIException('$_defaultErrorMessage: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
class CalendarEventBooking {
|
||||||
|
final String uuid;
|
||||||
|
final DateTime date;
|
||||||
|
final String startTime;
|
||||||
|
final String endTime;
|
||||||
|
final int cost;
|
||||||
|
final BookingUser user;
|
||||||
|
final BookingSpace space;
|
||||||
|
|
||||||
|
CalendarEventBooking({
|
||||||
|
required this.uuid,
|
||||||
|
required this.date,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
required this.cost,
|
||||||
|
required this.user,
|
||||||
|
required this.space,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CalendarEventBooking.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CalendarEventBooking(
|
||||||
|
uuid: json['uuid'] as String? ?? '',
|
||||||
|
date: json['date'] != null
|
||||||
|
? DateTime.parse(json['date'] as String)
|
||||||
|
: DateTime.now(),
|
||||||
|
startTime: json['startTime'] as String? ?? '',
|
||||||
|
endTime: json['endTime'] as String? ?? '',
|
||||||
|
cost: _parseInt(json['cost']),
|
||||||
|
user: json['user'] != null
|
||||||
|
? BookingUser.fromJson(json['user'] as Map<String, dynamic>)
|
||||||
|
: BookingUser.empty(),
|
||||||
|
space: json['space'] != null
|
||||||
|
? BookingSpace.fromJson(json['space'] as Map<String, dynamic>)
|
||||||
|
: BookingSpace.empty(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _parseInt(dynamic value) {
|
||||||
|
if (value is int) return value;
|
||||||
|
if (value is String) return int.tryParse(value) ?? 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BookingUser {
|
||||||
|
final String uuid;
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String email;
|
||||||
|
final String? companyName;
|
||||||
|
|
||||||
|
BookingUser({
|
||||||
|
required this.uuid,
|
||||||
|
required this.firstName,
|
||||||
|
required this.lastName,
|
||||||
|
required this.email,
|
||||||
|
this.companyName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BookingUser.fromJson(Map<String, dynamic> json) {
|
||||||
|
return BookingUser(
|
||||||
|
uuid: json['uuid'] as String? ?? '',
|
||||||
|
firstName: json['firstName'] as String? ?? '',
|
||||||
|
lastName: json['lastName'] as String? ?? '',
|
||||||
|
email: json['email'] as String? ?? '',
|
||||||
|
companyName: json['companyName'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BookingUser.empty() {
|
||||||
|
return BookingUser(
|
||||||
|
uuid: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
email: '',
|
||||||
|
companyName: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BookingSpace {
|
||||||
|
final String uuid;
|
||||||
|
final String spaceName;
|
||||||
|
|
||||||
|
BookingSpace({
|
||||||
|
required this.uuid,
|
||||||
|
required this.spaceName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BookingSpace.fromJson(Map<String, dynamic> json) {
|
||||||
|
return BookingSpace(
|
||||||
|
uuid: json['uuid'] as String? ?? '',
|
||||||
|
spaceName: json['spaceName'] as String? ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BookingSpace.empty() {
|
||||||
|
return BookingSpace(
|
||||||
|
uuid: '',
|
||||||
|
spaceName: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CalendarEventsResponse {
|
||||||
|
final int statusCode;
|
||||||
|
final String message;
|
||||||
|
final List<CalendarEventBooking> data;
|
||||||
|
final bool success;
|
||||||
|
|
||||||
|
CalendarEventsResponse({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.message,
|
||||||
|
required this.data,
|
||||||
|
required this.success,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CalendarEventsResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CalendarEventsResponse(
|
||||||
|
statusCode: _parseInt(json['statusCode']),
|
||||||
|
message: json['message'] as String? ?? '',
|
||||||
|
data: (json['data'] as List? ?? [])
|
||||||
|
.map((e) => CalendarEventBooking.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
success: json['success'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _parseInt(dynamic value) {
|
||||||
|
if (value is int) return value;
|
||||||
|
if (value is String) return int.tryParse(value) ?? 0;
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||||
|
|
||||||
|
abstract class CalendarSystemService {
|
||||||
|
Future<CalendarEventsResponse> getCalendarEvents({
|
||||||
|
required String spaceId,
|
||||||
|
});
|
||||||
|
}
|
@ -2,13 +2,17 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
|
||||||
|
|
||||||
part 'events_event.dart';
|
part 'events_event.dart';
|
||||||
part 'events_state.dart';
|
part 'events_state.dart';
|
||||||
|
|
||||||
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||||
final EventController eventController = EventController();
|
final EventController eventController = EventController();
|
||||||
|
final CalendarSystemService calendarService;
|
||||||
|
|
||||||
CalendarEventsBloc() : super(EventsInitial()) {
|
CalendarEventsBloc({required this.calendarService}) : super(EventsInitial()) {
|
||||||
on<LoadEvents>(_onLoadEvents);
|
on<LoadEvents>(_onLoadEvents);
|
||||||
on<AddEvent>(_onAddEvent);
|
on<AddEvent>(_onAddEvent);
|
||||||
on<StartTimer>(_onStartTimer);
|
on<StartTimer>(_onStartTimer);
|
||||||
@ -22,53 +26,24 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
|||||||
) async {
|
) async {
|
||||||
emit(EventsLoading());
|
emit(EventsLoading());
|
||||||
try {
|
try {
|
||||||
final events = _generateDummyEventsForWeek(event.weekStart);
|
final response = await calendarService.getCalendarEvents(
|
||||||
|
spaceId: event.spaceId,
|
||||||
|
);
|
||||||
|
final events =
|
||||||
|
response.data.map<CalendarEventData>(_toCalendarEventData).toList();
|
||||||
eventController.addAll(events);
|
eventController.addAll(events);
|
||||||
emit(EventsLoaded(
|
emit(EventsLoaded(events: events));
|
||||||
events: events,
|
|
||||||
initialDate: event.weekStart,
|
|
||||||
weekDays: _getWeekDays(event.weekStart),
|
|
||||||
));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(EventsError('Failed to load events'));
|
emit(EventsError('Failed to load events'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CalendarEventData> _generateDummyEventsForWeek(DateTime weekStart) {
|
|
||||||
final events = <CalendarEventData>[];
|
|
||||||
|
|
||||||
for (int i = 0; i < 7; i++) {
|
|
||||||
final date = weekStart.add(Duration(days: i));
|
|
||||||
|
|
||||||
events.add(CalendarEventData(
|
|
||||||
date: date,
|
|
||||||
startTime: date.copyWith(hour: 9, minute: 0),
|
|
||||||
endTime: date.copyWith(hour: 10, minute: 30),
|
|
||||||
title: 'Team Meeting',
|
|
||||||
description: 'Daily standup',
|
|
||||||
color: Colors.blue,
|
|
||||||
));
|
|
||||||
events.add(CalendarEventData(
|
|
||||||
date: date,
|
|
||||||
startTime: date.copyWith(hour: 14, minute: 0),
|
|
||||||
endTime: date.copyWith(hour: 15, minute: 0),
|
|
||||||
title: 'Client Call',
|
|
||||||
description: 'Project discussion',
|
|
||||||
color: Colors.green,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onAddEvent(AddEvent event, Emitter<CalendarEventState> emit) {
|
void _onAddEvent(AddEvent event, Emitter<CalendarEventState> emit) {
|
||||||
eventController.add(event.event);
|
eventController.add(event.event);
|
||||||
if (state is EventsLoaded) {
|
if (state is EventsLoaded) {
|
||||||
final loaded = state as EventsLoaded;
|
final loaded = state as EventsLoaded;
|
||||||
emit(EventsLoaded(
|
emit(EventsLoaded(
|
||||||
events: [...eventController.events],
|
events: [...eventController.events],
|
||||||
initialDate: loaded.initialDate,
|
|
||||||
weekDays: loaded.weekDays,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,47 +61,44 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
|||||||
final newWeekDays = _getWeekDays(event.weekDate);
|
final newWeekDays = _getWeekDays(event.weekDate);
|
||||||
emit(EventsLoaded(
|
emit(EventsLoaded(
|
||||||
events: loaded.events,
|
events: loaded.events,
|
||||||
initialDate: event.weekDate,
|
|
||||||
weekDays: newWeekDays,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CalendarEventData> _generateDummyEvents() {
|
CalendarEventData _toCalendarEventData(CalendarEventBooking booking) {
|
||||||
final now = DateTime.now();
|
final date = booking.date;
|
||||||
return [
|
|
||||||
CalendarEventData(
|
final localDate = date.toLocal();
|
||||||
date: now,
|
|
||||||
startTime: now.copyWith(hour: 8, minute: 00, second: 0),
|
final startParts = booking.startTime.split(':').map(int.parse).toList();
|
||||||
endTime: now.copyWith(hour: 9, minute: 00, second: 0),
|
final endParts = booking.endTime.split(':').map(int.parse).toList();
|
||||||
title: 'Team Meeting',
|
|
||||||
description: 'Weekly team sync',
|
final startTime = DateTime(
|
||||||
|
localDate.year,
|
||||||
|
localDate.month,
|
||||||
|
localDate.day,
|
||||||
|
startParts[0],
|
||||||
|
startParts[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
final endTime = DateTime(
|
||||||
|
localDate.year,
|
||||||
|
localDate.month,
|
||||||
|
localDate.day,
|
||||||
|
endParts[0],
|
||||||
|
endParts[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
return CalendarEventData(
|
||||||
|
date: startTime,
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
title:
|
||||||
|
'${booking.space.spaceName} - ${booking.user.firstName} ${booking.user.lastName}',
|
||||||
|
description: 'Cost: ${booking.cost}',
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
),
|
event: booking,
|
||||||
CalendarEventData(
|
);
|
||||||
date: now,
|
|
||||||
startTime: now.copyWith(hour: 9, minute: 00, second: 0),
|
|
||||||
endTime: now.copyWith(hour: 10, minute: 30, second: 0),
|
|
||||||
title: 'Team Meeting',
|
|
||||||
description: 'Weekly team sync',
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
CalendarEventData(
|
|
||||||
date: now.add(const Duration(days: 1)),
|
|
||||||
startTime: now.copyWith(hour: 14, day: now.day + 1),
|
|
||||||
endTime: now.copyWith(hour: 15, day: now.day + 1),
|
|
||||||
title: 'Client Call',
|
|
||||||
description: 'Project discussion',
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
CalendarEventData(
|
|
||||||
date: now.add(const Duration(days: 2)),
|
|
||||||
startTime: now.copyWith(hour: 11, day: now.day + 2),
|
|
||||||
endTime: now.copyWith(hour: 12, day: now.day + 2),
|
|
||||||
title: 'Lunch with Team',
|
|
||||||
color: Colors.orange,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DateTime> _getWeekDays(DateTime date) {
|
List<DateTime> _getWeekDays(DateTime date) {
|
||||||
|
@ -6,13 +6,20 @@ abstract class CalendarEventsEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadEvents extends CalendarEventsEvent {
|
class LoadEvents extends CalendarEventsEvent {
|
||||||
|
final String spaceId;
|
||||||
final DateTime weekStart;
|
final DateTime weekStart;
|
||||||
const LoadEvents({required this.weekStart});
|
final DateTime weekEnd;
|
||||||
|
|
||||||
|
const LoadEvents({
|
||||||
|
required this.spaceId,
|
||||||
|
required this.weekStart,
|
||||||
|
required this.weekEnd,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddEvent extends CalendarEventsEvent {
|
class AddEvent extends CalendarEventsEvent {
|
||||||
final CalendarEventData event;
|
final CalendarEventData event;
|
||||||
AddEvent(this.event);
|
const AddEvent(this.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
class StartTimer extends CalendarEventsEvent {}
|
class StartTimer extends CalendarEventsEvent {}
|
||||||
@ -23,3 +30,8 @@ class GoToWeek extends CalendarEventsEvent {
|
|||||||
final DateTime weekDate;
|
final DateTime weekDate;
|
||||||
GoToWeek(this.weekDate);
|
GoToWeek(this.weekDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CheckWeekHasEvents extends CalendarEventsEvent {
|
||||||
|
final DateTime weekStart;
|
||||||
|
const CheckWeekHasEvents(this.weekStart);
|
||||||
|
}
|
||||||
|
@ -9,13 +9,9 @@ class EventsLoading extends CalendarEventState {}
|
|||||||
|
|
||||||
class EventsLoaded extends CalendarEventState {
|
class EventsLoaded extends CalendarEventState {
|
||||||
final List<CalendarEventData> events;
|
final List<CalendarEventData> events;
|
||||||
final DateTime initialDate;
|
|
||||||
final List<DateTime> weekDays;
|
|
||||||
|
|
||||||
EventsLoaded({
|
EventsLoaded({
|
||||||
required this.events,
|
required this.events,
|
||||||
required this.initialDate,
|
|
||||||
required this.weekDays,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,12 @@ import 'package:bloc/bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
||||||
import 'date_selection_state.dart';
|
import 'date_selection_state.dart';
|
||||||
|
|
||||||
|
|
||||||
class DateSelectionBloc extends Bloc<DateSelectionEvent, DateSelectionState> {
|
class DateSelectionBloc extends Bloc<DateSelectionEvent, DateSelectionState> {
|
||||||
DateSelectionBloc() : super(DateSelectionState.initial()) {
|
DateSelectionBloc() : super(DateSelectionState.initial()) {
|
||||||
on<SelectDate>((event, emit) {
|
on<SelectDate>((event, emit) {
|
||||||
final newWeekStart = _getStartOfWeek(event.selectedDate);
|
final newWeekStart = _getStartOfWeek(event.selectedDate);
|
||||||
emit(DateSelectionState(
|
emit(state.copyWith(
|
||||||
selectedDate: event.selectedDate,
|
selectedDate: event.selectedDate,
|
||||||
weekStart: newWeekStart,
|
weekStart: newWeekStart,
|
||||||
));
|
));
|
||||||
@ -14,19 +15,21 @@ class DateSelectionBloc extends Bloc<DateSelectionEvent, DateSelectionState> {
|
|||||||
|
|
||||||
on<NextWeek>((event, emit) {
|
on<NextWeek>((event, emit) {
|
||||||
final newWeekStart = state.weekStart.add(const Duration(days: 7));
|
final newWeekStart = state.weekStart.add(const Duration(days: 7));
|
||||||
final inNewWeek = state.selectedDate
|
emit(state.copyWith(
|
||||||
.isAfter(newWeekStart.subtract(const Duration(days: 1))) &&
|
|
||||||
state.selectedDate
|
|
||||||
.isBefore(newWeekStart.add(const Duration(days: 7)));
|
|
||||||
emit(DateSelectionState(
|
|
||||||
selectedDate: state.selectedDate,
|
|
||||||
weekStart: newWeekStart,
|
weekStart: newWeekStart,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
on<PreviousWeek>((event, emit) {
|
on<PreviousWeek>((event, emit) {
|
||||||
emit(DateSelectionState(
|
final newWeekStart = state.weekStart.subtract(const Duration(days: 7));
|
||||||
selectedDate: state.selectedDate!.subtract(const Duration(days: 7)),
|
emit(state.copyWith(
|
||||||
weekStart: state.weekStart.subtract(const Duration(days: 7)),
|
weekStart: newWeekStart,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
on<SelectDateFromSidebarCalendar>((event, emit) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
selectedDateFromSideBarCalender: event.selectedDate,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,3 +11,8 @@ class SelectDate extends DateSelectionEvent {
|
|||||||
class NextWeek extends DateSelectionEvent {}
|
class NextWeek extends DateSelectionEvent {}
|
||||||
|
|
||||||
class PreviousWeek extends DateSelectionEvent {}
|
class PreviousWeek extends DateSelectionEvent {}
|
||||||
|
|
||||||
|
class SelectDateFromSidebarCalendar extends DateSelectionEvent {
|
||||||
|
final DateTime selectedDate;
|
||||||
|
SelectDateFromSidebarCalendar(this.selectedDate);
|
||||||
|
}
|
@ -1,21 +1,34 @@
|
|||||||
|
|
||||||
class DateSelectionState {
|
class DateSelectionState {
|
||||||
final DateTime selectedDate;
|
final DateTime selectedDate;
|
||||||
final DateTime weekStart;
|
final DateTime weekStart;
|
||||||
|
final DateTime? selectedDateFromSideBarCalender;
|
||||||
|
|
||||||
const DateSelectionState({
|
DateSelectionState({
|
||||||
required this.selectedDate,
|
required this.selectedDate,
|
||||||
required this.weekStart,
|
required this.weekStart,
|
||||||
|
this.selectedDateFromSideBarCalender,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory DateSelectionState.initial() {
|
factory DateSelectionState.initial() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
final weekStart = now.subtract(Duration(days: now.weekday - 1));
|
||||||
return DateSelectionState(
|
return DateSelectionState(
|
||||||
selectedDate: now,
|
selectedDate: now,
|
||||||
weekStart: _getStartOfWeek(now),
|
weekStart: weekStart,
|
||||||
|
selectedDateFromSideBarCalender: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DateTime _getStartOfWeek(DateTime date) {
|
DateSelectionState copyWith({
|
||||||
return date.subtract(Duration(days: date.weekday - 1));
|
DateTime? selectedDate,
|
||||||
|
DateTime? weekStart,
|
||||||
|
DateTime? selectedDateFromSideBarCalender,
|
||||||
|
}) {
|
||||||
|
return DateSelectionState(
|
||||||
|
selectedDate: selectedDate ?? this.selectedDate,
|
||||||
|
weekStart: weekStart ?? this.weekStart,
|
||||||
|
selectedDateFromSideBarCalender: selectedDateFromSideBarCalender ?? this.selectedDateFromSideBarCalender,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart';
|
||||||
@ -9,7 +10,9 @@ import 'package:syncrow_web/pages/access_management/booking_system/presentation/
|
|||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_navigation.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
@ -35,33 +38,20 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CalendarEventData> _generateDummyEventsForWeek(DateTime weekStart) {
|
void _dispatchLoadEvents(BuildContext context) {
|
||||||
final List<CalendarEventData> events = [];
|
final selectedRoom =
|
||||||
for (int i = 0; i < 7; i++) {
|
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
|
||||||
final date = weekStart.add(Duration(days: i));
|
final dateState = context.read<DateSelectionBloc>().state;
|
||||||
events.add(CalendarEventData(
|
|
||||||
date: date,
|
|
||||||
startTime: date.copyWith(hour: 9, minute: 0),
|
|
||||||
endTime: date.copyWith(hour: 10, minute: 30),
|
|
||||||
title: 'Team Meeting',
|
|
||||||
description: 'Daily standup',
|
|
||||||
color: Colors.blue,
|
|
||||||
));
|
|
||||||
events.add(CalendarEventData(
|
|
||||||
date: date,
|
|
||||||
startTime: date.copyWith(hour: 14, minute: 0),
|
|
||||||
endTime: date.copyWith(hour: 15, minute: 0),
|
|
||||||
title: 'Client Call',
|
|
||||||
description: 'Project discussion',
|
|
||||||
color: Colors.green,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadEventsForWeek(DateTime weekStart) {
|
if (selectedRoom != null) {
|
||||||
_eventController.removeWhere((_) => true);
|
context.read<CalendarEventsBloc>().add(
|
||||||
_eventController.addAll(_generateDummyEventsForWeek(weekStart));
|
LoadEvents(
|
||||||
|
spaceId: selectedRoom.uuid,
|
||||||
|
weekStart: dateState.weekStart,
|
||||||
|
weekEnd: dateState.weekStart.add(const Duration(days: 6)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -70,13 +60,28 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
providers: [
|
providers: [
|
||||||
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
||||||
BlocProvider(create: (_) => DateSelectionBloc()),
|
BlocProvider(create: (_) => DateSelectionBloc()),
|
||||||
|
BlocProvider(
|
||||||
|
create: (_) => CalendarEventsBloc(
|
||||||
|
calendarService:
|
||||||
|
FakeRemoteCalendarService(HTTPService(), useDummy: true),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
child: Builder(
|
||||||
listenWhen: (previous, current) =>
|
builder: (context) =>
|
||||||
previous.weekStart != current.weekStart,
|
BlocListener<CalendarEventsBloc, CalendarEventState>(
|
||||||
|
listenWhen: (prev, curr) => curr is EventsLoaded,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
_loadEventsForWeek(state.weekStart);
|
if (state is EventsLoaded) {
|
||||||
|
_eventController.removeWhere((_) => true);
|
||||||
|
_eventController.addAll(state.events);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
child: BlocListener<SelectedBookableSpaceBloc,
|
||||||
|
SelectedBookableSpaceState>(
|
||||||
|
listener: (context, state) => _dispatchLoadEvents(context),
|
||||||
|
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
||||||
|
listener: (context, state) => _dispatchLoadEvents(context),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -111,7 +116,8 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
child: BlocBuilder<DateSelectionBloc,
|
||||||
|
DateSelectionState>(
|
||||||
builder: (context, dateState) {
|
builder: (context, dateState) {
|
||||||
return CustomCalendarPage(
|
return CustomCalendarPage(
|
||||||
selectedDate: dateState.selectedDate,
|
selectedDate: dateState.selectedDate,
|
||||||
@ -120,6 +126,8 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
context
|
context
|
||||||
.read<DateSelectionBloc>()
|
.read<DateSelectionBloc>()
|
||||||
.add(SelectDate(newDate));
|
.add(SelectDate(newDate));
|
||||||
|
context.read<DateSelectionBloc>().add(
|
||||||
|
SelectDateFromSidebarCalendar(newDate));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -154,59 +162,25 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
BlocBuilder<DateSelectionBloc,
|
||||||
|
DateSelectionState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final weekStart = state.weekStart;
|
final weekStart = state.weekStart;
|
||||||
final weekEnd =
|
final weekEnd =
|
||||||
weekStart.add(const Duration(days: 6));
|
weekStart.add(const Duration(days: 6));
|
||||||
return Container(
|
return WeekNavigation(
|
||||||
padding: const EdgeInsets.symmetric(
|
weekStart: weekStart,
|
||||||
horizontal: 10, vertical: 5),
|
weekEnd: weekEnd,
|
||||||
decoration: BoxDecoration(
|
onPreviousWeek: () {
|
||||||
color: ColorsManager.circleRolesBackground,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
blurRadius: 4,
|
|
||||||
offset: Offset(0, 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
iconSize: 15,
|
|
||||||
icon: const Icon(Icons.arrow_back_ios,
|
|
||||||
color: ColorsManager.lightGrayColor),
|
|
||||||
onPressed: () {
|
|
||||||
context
|
context
|
||||||
.read<DateSelectionBloc>()
|
.read<DateSelectionBloc>()
|
||||||
.add(PreviousWeek());
|
.add(PreviousWeek());
|
||||||
},
|
},
|
||||||
),
|
onNextWeek: () {
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(
|
|
||||||
_getMonthYearText(weekStart, weekEnd),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
IconButton(
|
|
||||||
iconSize: 15,
|
|
||||||
icon: const Icon(Icons.arrow_forward_ios,
|
|
||||||
color: ColorsManager.lightGrayColor),
|
|
||||||
onPressed: () {
|
|
||||||
context
|
context
|
||||||
.read<DateSelectionBloc>()
|
.read<DateSelectionBloc>()
|
||||||
.add(NextWeek());
|
.add(NextWeek());
|
||||||
},
|
},
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -216,17 +190,35 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
child: BlocBuilder<SelectedBookableSpaceBloc,
|
child: BlocBuilder<SelectedBookableSpaceBloc,
|
||||||
SelectedBookableSpaceState>(
|
SelectedBookableSpaceState>(
|
||||||
builder: (context, roomState) {
|
builder: (context, roomState) {
|
||||||
final selectedRoom = roomState.selectedBookableSpace;
|
final selectedRoom =
|
||||||
|
roomState.selectedBookableSpace;
|
||||||
return BlocBuilder<DateSelectionBloc,
|
return BlocBuilder<DateSelectionBloc,
|
||||||
DateSelectionState>(
|
DateSelectionState>(
|
||||||
builder: (context, dateState) {
|
builder: (context, dateState) {
|
||||||
return WeeklyCalendarPage(
|
return BlocListener<CalendarEventsBloc,
|
||||||
startTime:
|
CalendarEventState>(
|
||||||
selectedRoom?.bookableConfig.startTime,
|
listenWhen: (prev, curr) =>
|
||||||
endTime: selectedRoom?.bookableConfig.endTime,
|
curr is EventsLoaded,
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state is EventsLoaded) {
|
||||||
|
_eventController
|
||||||
|
.removeWhere((_) => true);
|
||||||
|
_eventController.addAll(state.events);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: WeeklyCalendarPage(
|
||||||
|
startTime: selectedRoom
|
||||||
|
?.bookableConfig.startTime,
|
||||||
|
endTime: selectedRoom
|
||||||
|
?.bookableConfig.endTime,
|
||||||
weekStart: dateState.weekStart,
|
weekStart: dateState.weekStart,
|
||||||
selectedDate: dateState.selectedDate,
|
selectedDate: dateState.selectedDate,
|
||||||
eventController: _eventController,
|
eventController: _eventController,
|
||||||
|
selectedDateFromSideBarCalender: context
|
||||||
|
.watch<DateSelectionBloc>()
|
||||||
|
.state
|
||||||
|
.selectedDateFromSideBarCalender,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -240,20 +232,9 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getMonthYearText(DateTime start, DateTime end) {
|
|
||||||
final startMonth = DateFormat('MMM').format(start);
|
|
||||||
final endMonth = DateFormat('MMM').format(end);
|
|
||||||
final year = start.year == end.year
|
|
||||||
? start.year.toString()
|
|
||||||
: '${start.year}-${end.year}';
|
|
||||||
|
|
||||||
if (start.month == end.month) {
|
|
||||||
return '$startMonth $year';
|
|
||||||
} else {
|
|
||||||
return '$startMonth - $endMonth $year';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class EventTileWidget extends StatelessWidget {
|
||||||
|
final List<CalendarEventData<Object?>> events;
|
||||||
|
|
||||||
|
const EventTileWidget({
|
||||||
|
super.key,
|
||||||
|
required this.events,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 2, horizontal: 2),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: events.map((event) {
|
||||||
|
final bool isEventEnded =
|
||||||
|
event.endTime != null && event.endTime!.isBefore(DateTime.now());
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isEventEnded
|
||||||
|
? ColorsManager.lightGrayBorderColor
|
||||||
|
: ColorsManager.blue1.withOpacity(0.25),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat('h:mm a').format(event.startTime!),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
event.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
class HatchedColumnBackground extends StatelessWidget {
|
||||||
|
final Color backgroundColor;
|
||||||
|
final Color lineColor;
|
||||||
|
final double opacity;
|
||||||
|
final double stripeSpacing;
|
||||||
|
final BorderRadius? borderRadius;
|
||||||
|
|
||||||
|
const HatchedColumnBackground({
|
||||||
|
super.key,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.lineColor,
|
||||||
|
this.opacity = 0.15,
|
||||||
|
this.stripeSpacing = 12,
|
||||||
|
this.borderRadius,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: _HatchedBackgroundPainter(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
opacity: opacity,
|
||||||
|
lineColor: lineColor,
|
||||||
|
stripeSpacing: stripeSpacing,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
),
|
||||||
|
size: Size.infinite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HatchedBackgroundPainter extends CustomPainter {
|
||||||
|
final Color backgroundColor;
|
||||||
|
final double opacity;
|
||||||
|
final Color lineColor;
|
||||||
|
final double stripeSpacing;
|
||||||
|
final BorderRadius? borderRadius;
|
||||||
|
|
||||||
|
_HatchedBackgroundPainter({
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.opacity,
|
||||||
|
required this.lineColor,
|
||||||
|
required this.stripeSpacing,
|
||||||
|
this.borderRadius,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
|
||||||
|
final RRect rrect = borderRadius?.toRRect(rect) ??
|
||||||
|
RRect.fromRectAndRadius(rect, Radius.zero);
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..color = backgroundColor.withOpacity(0.02)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
canvas.drawRRect(rrect, backgroundPaint);
|
||||||
|
canvas.save();
|
||||||
|
canvas.clipRRect(rrect);
|
||||||
|
final linePaint = Paint()
|
||||||
|
..color = lineColor
|
||||||
|
..strokeWidth = 0.5
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
final maxExtent =
|
||||||
|
math.sqrt(size.width * size.width + size.height * size.height);
|
||||||
|
|
||||||
|
canvas.translate(0, size.height);
|
||||||
|
canvas.rotate(-math.pi / 4);
|
||||||
|
double y = -maxExtent;
|
||||||
|
while (y < maxExtent) {
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(-maxExtent, y),
|
||||||
|
Offset(maxExtent, y),
|
||||||
|
linePaint,
|
||||||
|
);
|
||||||
|
y += stripeSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _HatchedBackgroundPainter oldDelegate) {
|
||||||
|
return backgroundColor != oldDelegate.backgroundColor ||
|
||||||
|
opacity != oldDelegate.opacity ||
|
||||||
|
lineColor != oldDelegate.lineColor ||
|
||||||
|
stripeSpacing != oldDelegate.stripeSpacing ||
|
||||||
|
borderRadius != oldDelegate.borderRadius;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class TimeLineWidget extends StatelessWidget {
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
const TimeLineWidget({Key? key, required this.date}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
int hour =
|
||||||
|
date.hour == 0 ? 12 : (date.hour > 12 ? date.hour - 12 : date.hour);
|
||||||
|
String period = date.hour >= 12 ? 'PM' : 'AM';
|
||||||
|
return Container(
|
||||||
|
height: 60,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '$hour',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 24,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
WidgetSpan(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 2, top: 6),
|
||||||
|
child: Text(
|
||||||
|
period,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
letterSpacing: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: PlaceholderAlignment.baseline,
|
||||||
|
baseline: TextBaseline.alphabetic,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class WeekDayHeader extends StatelessWidget {
|
||||||
|
final DateTime date;
|
||||||
|
final bool isSelectedDay;
|
||||||
|
|
||||||
|
const WeekDayHeader({
|
||||||
|
Key? key,
|
||||||
|
required this.date,
|
||||||
|
required this.isSelectedDay,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat('EEE').format(date).toUpperCase(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
color: isSelectedDay ? Colors.blue : Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
DateFormat('d').format(date),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 20,
|
||||||
|
color:
|
||||||
|
isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class WeekNavigation extends StatelessWidget {
|
||||||
|
final DateTime weekStart;
|
||||||
|
final DateTime weekEnd;
|
||||||
|
final VoidCallback onPreviousWeek;
|
||||||
|
final VoidCallback onNextWeek;
|
||||||
|
|
||||||
|
const WeekNavigation({
|
||||||
|
Key? key,
|
||||||
|
required this.weekStart,
|
||||||
|
required this.weekEnd,
|
||||||
|
required this.onPreviousWeek,
|
||||||
|
required this.onNextWeek,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.circleRolesBackground,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
iconSize: 15,
|
||||||
|
icon: const Icon(Icons.arrow_back_ios,
|
||||||
|
color: ColorsManager.lightGrayColor),
|
||||||
|
onPressed: onPreviousWeek,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
_getMonthYearText(weekStart, weekEnd),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 15,
|
||||||
|
icon: const Icon(Icons.arrow_forward_ios,
|
||||||
|
color: ColorsManager.lightGrayColor),
|
||||||
|
onPressed: onNextWeek,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getMonthYearText(DateTime start, DateTime end) {
|
||||||
|
final startMonth = DateFormat('MMM').format(start);
|
||||||
|
final endMonth = DateFormat('MMM').format(end);
|
||||||
|
final year = start.year == end.year
|
||||||
|
? start.year.toString()
|
||||||
|
: '${start.year}-${end.year}';
|
||||||
|
|
||||||
|
if (start.month == end.month) {
|
||||||
|
return '$startMonth $year';
|
||||||
|
} else {
|
||||||
|
return '$startMonth - $endMonth $year';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class WeeklyCalendarPage extends StatelessWidget {
|
class WeeklyCalendarPage extends StatelessWidget {
|
||||||
@ -9,6 +12,7 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
final EventController eventController;
|
final EventController eventController;
|
||||||
final String? startTime;
|
final String? startTime;
|
||||||
final String? endTime;
|
final String? endTime;
|
||||||
|
final DateTime? selectedDateFromSideBarCalender;
|
||||||
|
|
||||||
const WeeklyCalendarPage({
|
const WeeklyCalendarPage({
|
||||||
super.key,
|
super.key,
|
||||||
@ -17,37 +21,81 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
required this.eventController,
|
required this.eventController,
|
||||||
this.startTime,
|
this.startTime,
|
||||||
this.endTime,
|
this.endTime,
|
||||||
|
this.selectedDateFromSideBarCalender,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final startHour = _parseHour(startTime, defaultValue: 0);
|
final startHour = _parseHour(startTime, defaultValue: 0);
|
||||||
final endHour = _parseHour(endTime, defaultValue: 24);
|
final endHour = _parseHour(endTime, defaultValue: 24);
|
||||||
|
|
||||||
if (endTime == null || endTime!.isEmpty) {
|
if (endTime == null || endTime!.isEmpty) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: Text(
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.calendar_today,
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
size: 80,
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
'Please select a bookable space to view the calendar.',
|
'Please select a bookable space to view the calendar.',
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: ColorsManager.lightGrayColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final weekDays = _getWeekDays(weekStart);
|
final weekDays = _getWeekDays(weekStart);
|
||||||
|
|
||||||
|
final selectedDayIndex =
|
||||||
|
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
||||||
|
final selectedSidebarIndex = selectedDateFromSideBarCalender == null
|
||||||
|
? -1
|
||||||
|
: weekDays
|
||||||
|
.indexWhere((d) => isSameDay(d, selectedDateFromSideBarCalender!));
|
||||||
|
|
||||||
|
const double timeLineWidth = 80;
|
||||||
|
const int totalDays = 7;
|
||||||
|
final DateTime highlightStart = DateTime(2025, 7, 10);
|
||||||
|
final DateTime highlightEnd = DateTime(2025, 7, 19);
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final double calendarWidth = constraints.maxWidth;
|
final double calendarWidth = constraints.maxWidth;
|
||||||
const double timeLineWidth = 80;
|
|
||||||
const int totalDays = 7;
|
|
||||||
final double dayColumnWidth =
|
final double dayColumnWidth =
|
||||||
(calendarWidth - timeLineWidth) / totalDays - 0.1;
|
(calendarWidth - timeLineWidth) / totalDays - 0.1;
|
||||||
final selectedDayIndex =
|
bool isInRange(DateTime date, DateTime start, DateTime end) {
|
||||||
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
return !date.isBefore(start) && !date.isAfter(end);
|
||||||
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25),
|
padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
WeekView(
|
WeekView(
|
||||||
|
weekDetectorBuilder: ({
|
||||||
|
required date,
|
||||||
|
required height,
|
||||||
|
required heightPerMinute,
|
||||||
|
required minuteSlotSize,
|
||||||
|
required width,
|
||||||
|
}) {
|
||||||
|
return isInRange(date, highlightStart, highlightEnd)
|
||||||
|
? HatchedColumnBackground(
|
||||||
|
backgroundColor: ColorsManager.grey800,
|
||||||
|
lineColor: ColorsManager.textGray,
|
||||||
|
opacity: 0.3,
|
||||||
|
stripeSpacing: 12,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
)
|
||||||
|
: const SizedBox();
|
||||||
|
},
|
||||||
pageViewPhysics: const NeverScrollableScrollPhysics(),
|
pageViewPhysics: const NeverScrollableScrollPhysics(),
|
||||||
key: ValueKey(weekStart),
|
key: ValueKey(weekStart),
|
||||||
controller: eventController,
|
controller: eventController,
|
||||||
@ -64,84 +112,19 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
height: 0,
|
height: 0,
|
||||||
),
|
),
|
||||||
weekDayBuilder: (date) {
|
weekDayBuilder: (date) {
|
||||||
final weekDays = _getWeekDays(weekStart);
|
return WeekDayHeader(
|
||||||
final selectedDayIndex =
|
date: date,
|
||||||
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
isSelectedDay: isSameDay(date, selectedDate),
|
||||||
final index = weekDays.indexWhere((d) => isSameDay(d, date));
|
|
||||||
final isSelectedDay = index == selectedDayIndex;
|
|
||||||
final isToday = isSameDay(date, DateTime.now());
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateFormat('EEE').format(date).toUpperCase(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 14,
|
|
||||||
color: isSelectedDay ? Colors.blue : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
DateFormat('d').format(date),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 20,
|
|
||||||
color: isSelectedDay
|
|
||||||
? ColorsManager.blue1
|
|
||||||
: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
timeLineBuilder: (date) {
|
timeLineBuilder: (date) {
|
||||||
int hour = date.hour == 0
|
return TimeLineWidget(date: date);
|
||||||
? 12
|
|
||||||
: (date.hour > 12 ? date.hour - 12 : date.hour);
|
|
||||||
String period = date.hour >= 12 ? 'PM' : 'AM';
|
|
||||||
return Container(
|
|
||||||
height: 60,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: '$hour',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 24,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
WidgetSpan(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 2, top: 6),
|
|
||||||
child: Text(
|
|
||||||
period,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 12,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
letterSpacing: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: PlaceholderAlignment.baseline,
|
|
||||||
baseline: TextBaseline.alphabetic,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
timeLineWidth: timeLineWidth,
|
timeLineWidth: timeLineWidth,
|
||||||
weekPageHeaderBuilder: (start, end) => Container(),
|
weekPageHeaderBuilder: (start, end) => Container(),
|
||||||
weekTitleHeight: 60,
|
weekTitleHeight: 60,
|
||||||
weekNumberBuilder: (firstDayOfWeek) => Padding(
|
weekNumberBuilder: (firstDayOfWeek) => Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(right: 15, bottom: 10),
|
||||||
right: 15,
|
|
||||||
bottom: 10,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
@ -158,49 +141,8 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventTileBuilder: (date, events, boundary, start, end) {
|
eventTileBuilder: (date, events, boundary, start, end) {
|
||||||
return Container(
|
return EventTileWidget(
|
||||||
margin:
|
events: events,
|
||||||
const EdgeInsets.symmetric(vertical: 2, horizontal: 2),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: events.map((event) {
|
|
||||||
final bool isEventEnded = event.endTime != null &&
|
|
||||||
event.endTime!.isBefore(DateTime.now());
|
|
||||||
return Expanded(
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isEventEnded
|
|
||||||
? ColorsManager.lightGrayBorderColor
|
|
||||||
: ColorsManager.blue1.withOpacity(0.25),
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateFormat('h:mm a').format(event.startTime!),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
event.title,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -219,6 +161,22 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (selectedSidebarIndex >= 0 &&
|
||||||
|
selectedSidebarIndex != selectedDayIndex)
|
||||||
|
Positioned(
|
||||||
|
left: (timeLineWidth + 3) +
|
||||||
|
(dayColumnWidth - 8) * (selectedSidebarIndex - 0.01),
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: dayColumnWidth,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 0, horizontal: 4),
|
||||||
|
color: Colors.orange.withOpacity(0.14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
top: 50,
|
top: 50,
|
||||||
|
@ -29,7 +29,9 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
height: 40,
|
height: 40,
|
||||||
|
borderRadius: 8,
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
backgroundColor: ColorsManager.boxColor,
|
backgroundColor: ColorsManager.boxColor,
|
||||||
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
||||||
@ -39,6 +41,8 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: isActive
|
child: isActive
|
||||||
? DefaultButton(
|
? DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
@ -49,10 +53,12 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: ColorsManager.red100,
|
||||||
child: const Text('Stop'),
|
child: const Text('Stop'),
|
||||||
)
|
)
|
||||||
: DefaultButton(
|
: DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
@ -63,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
countDownCode: countDownCode),
|
countDownCode: countDownCode),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: ColorsManager.primaryColor,
|
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -226,6 +226,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
index.toString().padLeft(2, '0'),
|
index.toString().padLeft(2, '0'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
color: isActive ? ColorsManager.grayColor : Colors.black,
|
color: isActive ? ColorsManager.grayColor : Colors.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -240,7 +241,8 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
label,
|
label,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: ColorsManager.grayColor,
|
color: ColorsManager.grayColor,
|
||||||
fontSize: 18,
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -31,12 +31,11 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => ScheduleBloc(
|
create: (_) => ScheduleBloc(deviceId: deviceUuid,)
|
||||||
deviceId: deviceUuid,
|
|
||||||
)
|
|
||||||
..add(ScheduleGetEvent(category: category))
|
..add(ScheduleGetEvent(category: category))
|
||||||
..add(ScheduleFetchStatusEvent(
|
..add(ScheduleFetchStatusEvent(
|
||||||
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
|
deviceId: deviceUuid,
|
||||||
|
countdownCode: countdownCode ?? '')),
|
||||||
child: Dialog(
|
child: Dialog(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
insetPadding: const EdgeInsets.all(20),
|
insetPadding: const EdgeInsets.all(20),
|
||||||
@ -77,7 +76,8 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
category: category,
|
category: category,
|
||||||
time: '',
|
time: '',
|
||||||
function: Status(
|
function: Status(
|
||||||
code: code.toString(), value: null),
|
code: code.toString(),
|
||||||
|
value: true),
|
||||||
days: [],
|
days: [],
|
||||||
),
|
),
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
|
@ -13,9 +13,9 @@ class ScheduleHeader extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'Scheduling',
|
'Scheduling',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
fontSize: 22,
|
fontWeight: FontWeight.w700,
|
||||||
color: ColorsManager.dialogBlueTitle,
|
fontSize: 30,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -27,7 +27,7 @@ class ScheduleManagementUI extends StatelessWidget {
|
|||||||
width: 170,
|
width: 170,
|
||||||
height: 40,
|
height: 40,
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderColor: ColorsManager.boxColor,
|
borderColor: ColorsManager.grayColor.withOpacity(0.5),
|
||||||
padding: 2,
|
padding: 2,
|
||||||
backgroundColor: ColorsManager.graysColor,
|
backgroundColor: ColorsManager.graysColor,
|
||||||
borderRadius: 15,
|
borderRadius: 15,
|
||||||
|
@ -19,6 +19,8 @@ class ScheduleModeButtons extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@ -33,9 +35,11 @@ class ScheduleModeButtons extends StatelessWidget {
|
|||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: onSave,
|
onPressed: onSave,
|
||||||
backgroundColor: ColorsManager.primaryColor,
|
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -35,12 +35,12 @@ class ScheduleModeSelector extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
children: [
|
||||||
_buildRadioTile(
|
_buildRadioTile(
|
||||||
context, 'Countdown', ScheduleModes.countdown, currentMode),
|
context, 'Countdown', ScheduleModes.countdown, currentMode),
|
||||||
_buildRadioTile(
|
_buildRadioTile(
|
||||||
context, 'Schedule', ScheduleModes.schedule, currentMode),
|
context, 'Schedule', ScheduleModes.schedule, currentMode),
|
||||||
|
const Spacer(flex: 1),
|
||||||
// _buildRadioTile(
|
// _buildRadioTile(
|
||||||
// context, 'Circulate', ScheduleModes.circulate, currentMode),
|
// context, 'Circulate', ScheduleModes.circulate, currentMode),
|
||||||
// _buildRadioTile(
|
// _buildRadioTile(
|
||||||
@ -65,6 +65,7 @@ class ScheduleModeSelector extends StatelessWidget {
|
|||||||
style: context.textTheme.bodySmall!.copyWith(
|
style: context.textTheme.bodySmall!.copyWith(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: Radio<ScheduleModes>(
|
leading: Radio<ScheduleModes>(
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class ScheduleDialogHelper {
|
class ScheduleDialogHelper {
|
||||||
static const List<String> allDays = [
|
static const List<String> allDays = [
|
||||||
@ -56,8 +58,9 @@ class ScheduleDialogHelper {
|
|||||||
Text(
|
Text(
|
||||||
isEdit ? 'Edit Schedule' : 'Add Schedule',
|
isEdit ? 'Edit Schedule' : 'Add Schedule',
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Colors.blue,
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 30,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(),
|
const SizedBox(),
|
||||||
@ -69,9 +72,9 @@ class ScheduleDialogHelper {
|
|||||||
height: 40,
|
height: 40,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.grey[200],
|
backgroundColor: ColorsManager.boxColor,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@ -110,19 +113,8 @@ class ScheduleDialogHelper {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
ScheduleModeButtons(
|
||||||
width: 100,
|
onSave: () {
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(ctx, null);
|
|
||||||
},
|
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 100,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
dynamic temp;
|
dynamic temp;
|
||||||
if (deviceType == 'CUR_2') {
|
if (deviceType == 'CUR_2') {
|
||||||
temp = functionOn! ? 'open' : 'close';
|
temp = functionOn! ? 'open' : 'close';
|
||||||
@ -141,8 +133,7 @@ class ScheduleDialogHelper {
|
|||||||
);
|
);
|
||||||
Navigator.pop(ctx, entry);
|
Navigator.pop(ctx, entry);
|
||||||
},
|
},
|
||||||
child: const Text('Save'),
|
),
|
||||||
)),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
|
||||||
|
class SpaceReorderDataModel {
|
||||||
|
const SpaceReorderDataModel({
|
||||||
|
required this.space,
|
||||||
|
this.parent,
|
||||||
|
this.community,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SpaceModel space;
|
||||||
|
final SpaceModel? parent;
|
||||||
|
final CommunityModel? community;
|
||||||
|
}
|
@ -16,21 +16,37 @@ import 'package:syncrow_web/services/api/http_service.dart';
|
|||||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
|
|
||||||
class SpaceManagementPage extends StatelessWidget {
|
class SpaceManagementPage extends StatefulWidget {
|
||||||
const SpaceManagementPage({super.key});
|
const SpaceManagementPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SpaceManagementPage> createState() => _SpaceManagementPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||||
|
late final CommunitiesBloc communitiesBloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
communitiesBloc = CommunitiesBloc(
|
||||||
|
communitiesService: DebouncedCommunitiesService(
|
||||||
|
RemoteCommunitiesService(HTTPService()),
|
||||||
|
),
|
||||||
|
)..add(const LoadCommunities(LoadCommunitiesParam()));
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
BlocProvider.value(value: communitiesBloc),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => CommunitiesBloc(
|
create: (context) => CommunitiesTreeSelectionBloc(
|
||||||
communitiesService: DebouncedCommunitiesService(
|
communitiesBloc: communitiesBloc,
|
||||||
RemoteCommunitiesService(HTTPService()),
|
|
||||||
),
|
),
|
||||||
)..add(const LoadCommunities(LoadCommunitiesParam())),
|
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()),
|
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => SpaceDetailsBloc(
|
create: (context) => SpaceDetailsBloc(
|
||||||
UniqueSubspacesDecorator(
|
UniqueSubspacesDecorator(
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_reorder_data_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_card_widget.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_card_widget.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_cell.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_cell.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class CommunityStructureCanvas extends StatefulWidget {
|
class CommunityStructureCanvas extends StatefulWidget {
|
||||||
const CommunityStructureCanvas({
|
const CommunityStructureCanvas({
|
||||||
@ -31,8 +35,9 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
final double _horizontalSpacing = 150.0;
|
final double _horizontalSpacing = 150.0;
|
||||||
final double _verticalSpacing = 120.0;
|
final double _verticalSpacing = 120.0;
|
||||||
|
|
||||||
late TransformationController _transformationController;
|
late final TransformationController _transformationController;
|
||||||
late AnimationController _animationController;
|
late final AnimationController _animationController;
|
||||||
|
SpaceReorderDataModel? _draggedData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -97,7 +102,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
final position = _positions[space.uuid];
|
final position = _positions[space.uuid];
|
||||||
if (position == null) return;
|
if (position == null) return;
|
||||||
|
|
||||||
const scale = 1.5;
|
const scale = 1;
|
||||||
final viewSize = context.size;
|
final viewSize = context.size;
|
||||||
if (viewSize == null) return;
|
if (viewSize == null) return;
|
||||||
|
|
||||||
@ -112,16 +117,33 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
_runAnimation(matrix);
|
_runAnimation(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onReorder(SpaceReorderDataModel data, int newIndex) {
|
||||||
|
final newCommunity = widget.community.copyWith();
|
||||||
|
final children = data.parent?.children ?? newCommunity.spaces;
|
||||||
|
final oldIndex = children.indexWhere((s) => s.uuid == data.space.uuid);
|
||||||
|
if (oldIndex != -1) {
|
||||||
|
final item = children.removeAt(oldIndex);
|
||||||
|
if (newIndex > oldIndex) {
|
||||||
|
children.insert(newIndex - 1, item);
|
||||||
|
} else {
|
||||||
|
children.insert(newIndex, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.read<CommunitiesBloc>().add(
|
||||||
|
CommunitiesUpdateCommunity(newCommunity),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _onSpaceTapped(SpaceModel? space) {
|
void _onSpaceTapped(SpaceModel? space) {
|
||||||
context.read<CommunitiesTreeSelectionBloc>().add(
|
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
SelectSpaceEvent(community: widget.community, space: space),
|
SelectSpaceEvent(community: widget.community, space: space),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resetSelectionAndZoom() {
|
void _resetSelectionAndZoom([CommunityModel? community]) {
|
||||||
context.read<CommunitiesTreeSelectionBloc>().add(
|
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
SelectSpaceEvent(
|
SelectSpaceEvent(
|
||||||
community: widget.community,
|
community: community ?? widget.community,
|
||||||
space: null,
|
space: null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -182,7 +204,8 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
_positions.clear();
|
_positions.clear();
|
||||||
final community = widget.community;
|
final community = widget.community;
|
||||||
|
|
||||||
_calculateLayout(community.spaces, 0, {});
|
final levelXOffset = <int, double>{};
|
||||||
|
_calculateLayout(community.spaces, 0, levelXOffset);
|
||||||
|
|
||||||
final selectedSpace = widget.selectedSpace;
|
final selectedSpace = widget.selectedSpace;
|
||||||
final highlightedUuids = <String>{};
|
final highlightedUuids = <String>{};
|
||||||
@ -193,7 +216,24 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
|
|
||||||
final widgets = <Widget>[];
|
final widgets = <Widget>[];
|
||||||
final connections = <SpaceConnectionModel>[];
|
final connections = <SpaceConnectionModel>[];
|
||||||
_generateWidgets(community.spaces, widgets, connections, highlightedUuids);
|
_generateWidgets(
|
||||||
|
widget.community.spaces,
|
||||||
|
widgets,
|
||||||
|
connections,
|
||||||
|
highlightedUuids,
|
||||||
|
community: widget.community,
|
||||||
|
);
|
||||||
|
|
||||||
|
final createButtonX = levelXOffset[0] ?? 0.0;
|
||||||
|
const createButtonY = 0.0;
|
||||||
|
|
||||||
|
widgets.add(
|
||||||
|
Positioned(
|
||||||
|
left: createButtonX,
|
||||||
|
top: createButtonY,
|
||||||
|
child: CreateSpaceButton(communityUuid: widget.community.uuid),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
@ -211,22 +251,30 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
List<SpaceModel> spaces,
|
List<SpaceModel> spaces,
|
||||||
List<Widget> widgets,
|
List<Widget> widgets,
|
||||||
List<SpaceConnectionModel> connections,
|
List<SpaceConnectionModel> connections,
|
||||||
Set<String> highlightedUuids,
|
Set<String> highlightedUuids, {
|
||||||
) {
|
CommunityModel? community,
|
||||||
for (final space in spaces) {
|
SpaceModel? parent,
|
||||||
|
}) {
|
||||||
|
if (spaces.isNotEmpty) {
|
||||||
|
final firstChildPos = _positions[spaces.first.uuid]!;
|
||||||
|
final targetPos = Offset(
|
||||||
|
firstChildPos.dx - (_horizontalSpacing / 4),
|
||||||
|
firstChildPos.dy,
|
||||||
|
);
|
||||||
|
widgets.add(_buildDropTarget(parent, community, 0, targetPos));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < spaces.length; i++) {
|
||||||
|
final space = spaces[i];
|
||||||
final position = _positions[space.uuid];
|
final position = _positions[space.uuid];
|
||||||
if (position == null) continue;
|
if (position == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
final isHighlighted = highlightedUuids.contains(space.uuid);
|
final isHighlighted = highlightedUuids.contains(space.uuid);
|
||||||
final hasNoSelectedSpace = widget.selectedSpace == null;
|
final hasNoSelectedSpace = widget.selectedSpace == null;
|
||||||
|
|
||||||
widgets.add(
|
final spaceCard = SpaceCardWidget(
|
||||||
Positioned(
|
|
||||||
left: position.dx,
|
|
||||||
top: position.dy,
|
|
||||||
width: _cardWidth,
|
|
||||||
height: _cardHeight,
|
|
||||||
child: SpaceCardWidget(
|
|
||||||
buildSpaceContainer: () {
|
buildSpaceContainer: () {
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
|
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
|
||||||
@ -241,28 +289,140 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
|
onTap: () => SpaceDetailsDialogHelper.showCreate(
|
||||||
|
context,
|
||||||
|
communityUuid: widget.community.uuid,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final reorderData = SpaceReorderDataModel(
|
||||||
|
space: space,
|
||||||
|
parent: parent,
|
||||||
|
community: community,
|
||||||
|
);
|
||||||
|
|
||||||
|
widgets.add(
|
||||||
|
Positioned(
|
||||||
|
left: position.dx,
|
||||||
|
top: position.dy,
|
||||||
|
width: _cardWidth,
|
||||||
|
height: _cardHeight,
|
||||||
|
child: Draggable<SpaceReorderDataModel>(
|
||||||
|
data: reorderData,
|
||||||
|
feedback: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.2,
|
||||||
|
child: SizedBox(
|
||||||
|
width: _cardWidth,
|
||||||
|
height: _cardHeight,
|
||||||
|
child: spaceCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDragStarted: () => setState(() => _draggedData = reorderData),
|
||||||
|
onDragEnd: (_) => setState(() => _draggedData = null),
|
||||||
|
onDraggableCanceled: (_, __) => setState(() => _draggedData = null),
|
||||||
|
childWhenDragging: Opacity(opacity: 0.4, child: spaceCard),
|
||||||
|
child: spaceCard,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final targetPos = Offset(
|
||||||
|
position.dx + _cardWidth + (_horizontalSpacing / 4) - 20,
|
||||||
|
position.dy,
|
||||||
|
);
|
||||||
|
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
|
||||||
|
|
||||||
for (final child in space.children) {
|
for (final child in space.children) {
|
||||||
connections.add(
|
connections.add(SpaceConnectionModel(from: space.uuid, to: child.uuid));
|
||||||
SpaceConnectionModel(from: space.uuid, to: child.uuid),
|
}
|
||||||
|
|
||||||
|
if (space.children.isNotEmpty) {
|
||||||
|
_generateWidgets(
|
||||||
|
space.children,
|
||||||
|
widgets,
|
||||||
|
connections,
|
||||||
|
highlightedUuids,
|
||||||
|
parent: space,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_generateWidgets(space.children, widgets, connections, highlightedUuids);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDropTarget(
|
||||||
|
SpaceModel? parent,
|
||||||
|
CommunityModel? community,
|
||||||
|
int index,
|
||||||
|
Offset position,
|
||||||
|
) {
|
||||||
|
return Positioned(
|
||||||
|
left: position.dx,
|
||||||
|
top: position.dy,
|
||||||
|
width: 40,
|
||||||
|
height: _cardHeight,
|
||||||
|
child: DragTarget<SpaceReorderDataModel>(
|
||||||
|
builder: (context, candidateData, rejectedData) {
|
||||||
|
if (_draggedData == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid &&
|
||||||
|
_draggedData?.community == null) ||
|
||||||
|
(_draggedData?.community?.uuid == community?.uuid &&
|
||||||
|
_draggedData?.parent == null);
|
||||||
|
|
||||||
|
if (!isTargetForDragged) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: 40,
|
||||||
|
height: _cardHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.theme.colorScheme.primary.withValues(
|
||||||
|
alpha: candidateData.isNotEmpty ? 0.7 : 0.3,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: context.theme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onWillAcceptWithDetails: (data) {
|
||||||
|
final children = parent?.children ?? community?.spaces ?? [];
|
||||||
|
final isSameParent = (data.data.parent?.uuid == parent?.uuid &&
|
||||||
|
data.data.community == null) ||
|
||||||
|
(data.data.community?.uuid == community?.uuid &&
|
||||||
|
data.data.parent == null);
|
||||||
|
|
||||||
|
if (!isSameParent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final oldIndex =
|
||||||
|
children.indexWhere((s) => s.uuid == data.data.space.uuid);
|
||||||
|
if (oldIndex == index || oldIndex == index - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onAcceptWithDetails: (data) => _onReorder(data.data, index),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final treeWidgets = _buildTreeWidgets();
|
final treeWidgets = _buildTreeWidgets();
|
||||||
return InteractiveViewer(
|
return InteractiveViewer(
|
||||||
transformationController: _transformationController,
|
transformationController: _transformationController,
|
||||||
boundaryMargin: EdgeInsets.symmetric(
|
boundaryMargin: EdgeInsets.symmetric(
|
||||||
horizontal: MediaQuery.sizeOf(context).width * 0.3,
|
horizontal: context.screenWidth * 0.3,
|
||||||
vertical: MediaQuery.sizeOf(context).height * 0.3,
|
vertical: context.screenHeight * 0.3,
|
||||||
),
|
),
|
||||||
minScale: 0.5,
|
minScale: 0.5,
|
||||||
maxScale: 3.0,
|
maxScale: 3.0,
|
||||||
@ -270,8 +430,8 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: _resetSelectionAndZoom,
|
onTap: _resetSelectionAndZoom,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: MediaQuery.sizeOf(context).width * 5,
|
width: context.screenWidth * 5,
|
||||||
height: MediaQuery.sizeOf(context).height * 5,
|
height: context.screenHeight * 5,
|
||||||
child: Stack(children: treeWidgets),
|
child: Stack(children: treeWidgets),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
@ -11,6 +14,26 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
|||||||
class CommunityStructureHeader extends StatelessWidget {
|
class CommunityStructureHeader extends StatelessWidget {
|
||||||
const CommunityStructureHeader({super.key});
|
const CommunityStructureHeader({super.key});
|
||||||
|
|
||||||
|
List<SpaceModel> _updateRecursive(
|
||||||
|
List<SpaceModel> spaces,
|
||||||
|
SpaceDetailsModel updatedSpace,
|
||||||
|
) {
|
||||||
|
return spaces.map((space) {
|
||||||
|
if (space.uuid == updatedSpace.uuid) {
|
||||||
|
return space.copyWith(
|
||||||
|
spaceName: updatedSpace.spaceName,
|
||||||
|
icon: updatedSpace.icon,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (space.children.isNotEmpty) {
|
||||||
|
return space.copyWith(
|
||||||
|
children: _updateRecursive(space.children, updatedSpace),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return space;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
@ -55,8 +78,9 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Community Structure',
|
'Community Structure',
|
||||||
style: theme.textTheme.headlineLarge
|
style: theme.textTheme.headlineLarge?.copyWith(
|
||||||
?.copyWith(color: ColorsManager.blackColor),
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (selectedCommunity != null)
|
if (selectedCommunity != null)
|
||||||
Row(
|
Row(
|
||||||
@ -67,8 +91,9 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
selectedCommunity.name,
|
selectedCommunity.name,
|
||||||
style: theme.textTheme.bodyLarge
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
?.copyWith(color: ColorsManager.blackColor),
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -93,12 +118,24 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
CommunityStructureHeaderActionButtons(
|
CommunityStructureHeaderActionButtons(
|
||||||
onDelete: (space) {},
|
onDelete: (space) {},
|
||||||
onDuplicate: (space) {},
|
onDuplicate: (space) {},
|
||||||
onEdit: (space) {
|
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
||||||
SpaceDetailsDialogHelper.showEdit(
|
|
||||||
context,
|
context,
|
||||||
spaceModel: selectedSpace!,
|
spaceModel: selectedSpace!,
|
||||||
|
communityUuid: selectedCommunity.uuid,
|
||||||
|
onSuccess: (updatedSpaceDetails) {
|
||||||
|
final communitiesBloc = context.read<CommunitiesBloc>();
|
||||||
|
final updatedSpaces = _updateRecursive(
|
||||||
|
selectedCommunity.spaces,
|
||||||
|
updatedSpaceDetails,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final community = selectedCommunity.copyWith(
|
||||||
|
spaces: updatedSpaces,
|
||||||
|
);
|
||||||
|
|
||||||
|
communitiesBloc.add(CommunitiesUpdateCommunity(community));
|
||||||
},
|
},
|
||||||
|
),
|
||||||
selectedSpace: selectedSpace,
|
selectedSpace: selectedSpace,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -2,42 +2,70 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class CreateSpaceButton extends StatelessWidget {
|
class CreateSpaceButton extends StatefulWidget {
|
||||||
const CreateSpaceButton({super.key});
|
const CreateSpaceButton({
|
||||||
|
required this.communityUuid,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String communityUuid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CreateSpaceButton> createState() => _CreateSpaceButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateSpaceButtonState extends State<CreateSpaceButton> {
|
||||||
|
bool _isHovered = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return Tooltip(
|
||||||
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
|
margin: const EdgeInsets.symmetric(vertical: 24),
|
||||||
|
message: 'Create a new space',
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => SpaceDetailsDialogHelper.showCreate(
|
||||||
|
context,
|
||||||
|
communityUuid: widget.communityUuid,
|
||||||
|
),
|
||||||
|
child: MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => _isHovered = true),
|
||||||
|
onExit: (_) => setState(() => _isHovered = false),
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
opacity: _isHovered ? 1.0 : 0.45,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 60,
|
width: 150,
|
||||||
|
height: 90,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withValues(alpha: 0.5),
|
color: Colors.grey.withValues(alpha: 0.2),
|
||||||
spreadRadius: 5,
|
spreadRadius: 3,
|
||||||
blurRadius: 7,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 3),
|
offset: const Offset(0, 4),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Center(
|
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 40,
|
margin: const EdgeInsets.symmetric(vertical: 20),
|
||||||
height: 40,
|
decoration: BoxDecoration(
|
||||||
decoration: const BoxDecoration(
|
border: Border.all(color: ColorsManager.borderColor, width: 2),
|
||||||
color: ColorsManager.boxColor,
|
color: ColorsManager.boxColor,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Center(
|
||||||
|
child: Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ class _SpaceCardWidgetState extends State<SpaceCardWidget> {
|
|||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
onEnter: (_) => setState(() => isHovered = true),
|
onEnter: (_) => setState(() => isHovered = true),
|
||||||
onExit: (_) => setState(() => isHovered = false),
|
onExit: (_) => setState(() => isHovered = false),
|
||||||
child: SizedBox(
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@ -38,7 +37,6 @@ class _SpaceCardWidgetState extends State<SpaceCardWidget> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ class SpaceCell extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 150,
|
width: 150,
|
||||||
|
@ -13,11 +13,17 @@ class SpaceManagementCommunityStructure extends StatelessWidget {
|
|||||||
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
||||||
final selectedCommunity = selectionBloc.selectedCommunity;
|
final selectedCommunity = selectionBloc.selectedCommunity;
|
||||||
final selectedSpace = selectionBloc.selectedSpace;
|
final selectedSpace = selectionBloc.selectedSpace;
|
||||||
const spacer = Spacer(flex: 10);
|
const spacer = Spacer(flex: 6);
|
||||||
return Visibility(
|
return Visibility(
|
||||||
visible: selectedCommunity!.spaces.isNotEmpty,
|
visible: selectedCommunity!.spaces.isNotEmpty,
|
||||||
replacement: const Row(
|
replacement: Row(
|
||||||
children: [spacer, Expanded(child: CreateSpaceButton()), spacer],
|
children: [
|
||||||
|
spacer,
|
||||||
|
Expanded(
|
||||||
|
child: CreateSpaceButton(communityUuid: selectedCommunity.uuid),
|
||||||
|
),
|
||||||
|
spacer
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -46,6 +46,25 @@ class SpaceModel extends Equatable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpaceModel copyWith({
|
||||||
|
String? uuid,
|
||||||
|
DateTime? createdAt,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
String? spaceName,
|
||||||
|
String? icon,
|
||||||
|
List<SpaceModel>? children,
|
||||||
|
}) {
|
||||||
|
return SpaceModel(
|
||||||
|
uuid: uuid ?? this.uuid,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
spaceName: spaceName ?? this.spaceName,
|
||||||
|
icon: icon ?? this.icon,
|
||||||
|
children: children ?? this.children,
|
||||||
|
parent: parent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [uuid, spaceName, icon, children];
|
List<Object?> get props => [uuid, spaceName, icon, children];
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,39 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||||
|
|
||||||
part 'communities_tree_selection_event.dart';
|
part 'communities_tree_selection_event.dart';
|
||||||
part 'communities_tree_selection_state.dart';
|
part 'communities_tree_selection_state.dart';
|
||||||
|
|
||||||
class CommunitiesTreeSelectionBloc
|
class CommunitiesTreeSelectionBloc
|
||||||
extends Bloc<CommunitiesTreeSelectionEvent, CommunitiesTreeSelectionState> {
|
extends Bloc<CommunitiesTreeSelectionEvent, CommunitiesTreeSelectionState> {
|
||||||
CommunitiesTreeSelectionBloc() : super(const CommunitiesTreeSelectionState()) {
|
CommunitiesTreeSelectionBloc({
|
||||||
|
required CommunitiesBloc communitiesBloc,
|
||||||
|
}) : _communitiesBloc = communitiesBloc,
|
||||||
|
super(const CommunitiesTreeSelectionState()) {
|
||||||
on<SelectCommunityEvent>(_onSelectCommunity);
|
on<SelectCommunityEvent>(_onSelectCommunity);
|
||||||
on<SelectSpaceEvent>(_onSelectSpace);
|
on<SelectSpaceEvent>(_onSelectSpace);
|
||||||
on<ClearCommunitiesTreeSelectionEvent>(_onClearSelection);
|
on<ClearCommunitiesTreeSelectionEvent>(_onClearSelection);
|
||||||
|
on<_CommunitiesStateUpdated>(_onCommunitiesStateUpdated);
|
||||||
|
|
||||||
|
_communitiesSubscription = _communitiesBloc.stream.listen((communitiesState) {
|
||||||
|
if (state.selectedCommunity != null) {
|
||||||
|
add(_CommunitiesStateUpdated(communitiesState));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final CommunitiesBloc _communitiesBloc;
|
||||||
|
late final StreamSubscription<CommunitiesState> _communitiesSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_communitiesSubscription.cancel();
|
||||||
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectCommunity(
|
void _onSelectCommunity(
|
||||||
@ -44,4 +66,59 @@ class CommunitiesTreeSelectionBloc
|
|||||||
) {
|
) {
|
||||||
emit(const CommunitiesTreeSelectionState());
|
emit(const CommunitiesTreeSelectionState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onCommunitiesStateUpdated(
|
||||||
|
_CommunitiesStateUpdated event,
|
||||||
|
Emitter<CommunitiesTreeSelectionState> emit,
|
||||||
|
) {
|
||||||
|
if (state.selectedCommunity == null) return;
|
||||||
|
|
||||||
|
final communities = event.communitiesState.communities;
|
||||||
|
try {
|
||||||
|
final updatedCommunity = communities.firstWhere(
|
||||||
|
(c) => c.uuid == state.selectedCommunity!.uuid,
|
||||||
|
);
|
||||||
|
|
||||||
|
var updatedSelectedSpace = state.selectedSpace;
|
||||||
|
if (state.selectedSpace != null) {
|
||||||
|
updatedSelectedSpace = _findSpaceInCommunity(
|
||||||
|
updatedCommunity,
|
||||||
|
state.selectedSpace!.uuid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
selectedCommunity: updatedCommunity,
|
||||||
|
selectedSpace: updatedSelectedSpace,
|
||||||
|
clearSelectedSpace: updatedSelectedSpace == null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
add(const ClearCommunitiesTreeSelectionEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceModel? _findSpaceInCommunity(CommunityModel community, String spaceUuid) {
|
||||||
|
try {
|
||||||
|
return _findSpaceRecursive(community.spaces, spaceUuid);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceModel _findSpaceRecursive(List<SpaceModel> spaces, String spaceUuid) {
|
||||||
|
for (final space in spaces) {
|
||||||
|
if (space.uuid == spaceUuid) {
|
||||||
|
return space;
|
||||||
|
}
|
||||||
|
if (space.children.isNotEmpty) {
|
||||||
|
try {
|
||||||
|
return _findSpaceRecursive(space.children, spaceUuid);
|
||||||
|
} catch (_) {
|
||||||
|
// not found in this branch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Exception('Space not found');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,3 +29,12 @@ final class ClearCommunitiesTreeSelectionEvent
|
|||||||
extends CommunitiesTreeSelectionEvent {
|
extends CommunitiesTreeSelectionEvent {
|
||||||
const ClearCommunitiesTreeSelectionEvent();
|
const ClearCommunitiesTreeSelectionEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class _CommunitiesStateUpdated extends CommunitiesTreeSelectionEvent {
|
||||||
|
const _CommunitiesStateUpdated(this.communitiesState);
|
||||||
|
|
||||||
|
final CommunitiesState communitiesState;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [communitiesState];
|
||||||
|
}
|
||||||
|
@ -12,18 +12,14 @@ final class CommunitiesTreeSelectionState extends Equatable {
|
|||||||
CommunitiesTreeSelectionState copyWith({
|
CommunitiesTreeSelectionState copyWith({
|
||||||
CommunityModel? selectedCommunity,
|
CommunityModel? selectedCommunity,
|
||||||
SpaceModel? selectedSpace,
|
SpaceModel? selectedSpace,
|
||||||
List<CommunityModel>? expandedCommunities,
|
bool clearSelectedSpace = false,
|
||||||
List<SpaceModel>? expandedSpaces,
|
|
||||||
}) {
|
}) {
|
||||||
return CommunitiesTreeSelectionState(
|
return CommunitiesTreeSelectionState(
|
||||||
selectedCommunity: selectedCommunity ?? this.selectedCommunity,
|
selectedCommunity: selectedCommunity ?? this.selectedCommunity,
|
||||||
selectedSpace: selectedSpace ?? this.selectedSpace,
|
selectedSpace: clearSelectedSpace ? null : selectedSpace ?? this.selectedSpace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [selectedCommunity, selectedSpace];
|
||||||
selectedCommunity,
|
}
|
||||||
selectedSpace,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
@ -13,14 +13,14 @@ class CommunitiesTreeFailureWidget extends StatelessWidget {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
SelectableText(
|
||||||
errorMessage ?? 'Something went wrong',
|
errorMessage ?? 'Something went wrong',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
FilledButton(
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => context.read<CommunitiesBloc>().add(
|
onPressed: () => context.read<CommunitiesBloc>().add(
|
||||||
LoadCommunities(
|
LoadCommunities(
|
||||||
LoadCommunitiesParam(
|
LoadCommunitiesParam(
|
||||||
|
@ -40,16 +40,6 @@ class SpaceDetailsModel extends Equatable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'uuid': uuid,
|
|
||||||
'spaceName': spaceName,
|
|
||||||
'icon': icon,
|
|
||||||
'productAllocations': productAllocations.map((e) => e.toJson()).toList(),
|
|
||||||
'subspaces': subspaces.map((e) => e.toJson()).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
SpaceDetailsModel copyWith({
|
SpaceDetailsModel copyWith({
|
||||||
String? uuid,
|
String? uuid,
|
||||||
String? spaceName,
|
String? spaceName,
|
||||||
@ -89,14 +79,6 @@ class ProductAllocation extends Equatable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'uuid': uuid,
|
|
||||||
'product': product.toJson(),
|
|
||||||
'tag': tag.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ProductAllocation copyWith({
|
ProductAllocation copyWith({
|
||||||
String? uuid,
|
String? uuid,
|
||||||
Product? product,
|
Product? product,
|
||||||
@ -134,14 +116,6 @@ class Subspace extends Equatable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'uuid': uuid,
|
|
||||||
'name': name,
|
|
||||||
'productAllocations': productAllocations.map((e) => e.toJson()).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Subspace copyWith({
|
Subspace copyWith({
|
||||||
String? uuid,
|
String? uuid,
|
||||||
String? name,
|
String? name,
|
||||||
|
@ -2,23 +2,37 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
abstract final class SpaceDetailsDialogHelper {
|
abstract final class SpaceDetailsDialogHelper {
|
||||||
static void showCreate(BuildContext context) {
|
static void showCreate(
|
||||||
|
BuildContext context, {
|
||||||
|
required String communityUuid,
|
||||||
|
}) {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => BlocProvider(
|
builder: (_) => MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider(
|
||||||
create: (context) => SpaceDetailsBloc(
|
create: (context) => SpaceDetailsBloc(
|
||||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||||
),
|
),
|
||||||
child: SpaceDetailsDialog(
|
),
|
||||||
|
],
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => SpaceDetailsDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: const SelectableText('Create Space'),
|
title: const SelectableText('Create Space'),
|
||||||
spaceModel: SpaceModel.empty(),
|
spaceModel: SpaceModel.empty(),
|
||||||
onSave: (space) {},
|
onSave: (space) {},
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -27,20 +41,98 @@ abstract final class SpaceDetailsDialogHelper {
|
|||||||
static void showEdit(
|
static void showEdit(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required SpaceModel spaceModel,
|
required SpaceModel spaceModel,
|
||||||
|
required String communityUuid,
|
||||||
|
required void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess,
|
||||||
}) {
|
}) {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => BlocProvider(
|
builder: (_) => MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider(
|
||||||
create: (context) => SpaceDetailsBloc(
|
create: (context) => SpaceDetailsBloc(
|
||||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => UpdateSpaceBloc(
|
||||||
|
RemoteUpdateSpaceService(HTTPService()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => BlocListener<UpdateSpaceBloc, UpdateSpaceState>(
|
||||||
|
listener: (context, state) => _updateListener(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
|
onSuccess,
|
||||||
|
),
|
||||||
child: SpaceDetailsDialog(
|
child: SpaceDetailsDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: const SelectableText('Edit Space'),
|
title: const SelectableText('Edit Space'),
|
||||||
spaceModel: spaceModel,
|
spaceModel: spaceModel,
|
||||||
onSave: (space) {},
|
onSave: (space) => context.read<UpdateSpaceBloc>().add(
|
||||||
|
UpdateSpace(
|
||||||
|
UpdateSpaceParam(
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
space: space,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _updateListener(
|
||||||
|
BuildContext context,
|
||||||
|
UpdateSpaceState state,
|
||||||
|
void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess,
|
||||||
|
) {
|
||||||
|
return switch (state) {
|
||||||
|
UpdateSpaceInitial() => null,
|
||||||
|
UpdateSpaceLoading() => _onLoading(context),
|
||||||
|
UpdateSpaceSuccess(:final space) =>
|
||||||
|
_onUpdateSuccess(context, space, onSuccess),
|
||||||
|
UpdateSpaceFailure(:final errorMessage) => _onError(context, errorMessage),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _onUpdateSuccess(
|
||||||
|
BuildContext context,
|
||||||
|
SpaceDetailsModel space,
|
||||||
|
void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess,
|
||||||
|
) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onSuccess?.call(space);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _onLoading(BuildContext context) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (_) => const Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _onError(BuildContext context, String errorMessage) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
title: const Text('Error'),
|
||||||
|
content: Text(errorMessage),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||||
@ -15,6 +14,7 @@ class SpaceDetailsDialog extends StatefulWidget {
|
|||||||
required this.spaceModel,
|
required this.spaceModel,
|
||||||
required this.onSave,
|
required this.onSave,
|
||||||
required this.context,
|
required this.context,
|
||||||
|
required this.communityUuid,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -22,6 +22,7 @@ class SpaceDetailsDialog extends StatefulWidget {
|
|||||||
final SpaceModel spaceModel;
|
final SpaceModel spaceModel;
|
||||||
final void Function(SpaceDetailsModel space) onSave;
|
final void Function(SpaceDetailsModel space) onSave;
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
|
final String communityUuid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SpaceDetailsDialog> createState() => _SpaceDetailsDialogState();
|
State<SpaceDetailsDialog> createState() => _SpaceDetailsDialogState();
|
||||||
@ -35,11 +36,7 @@ class _SpaceDetailsDialogState extends State<SpaceDetailsDialog> {
|
|||||||
if (!isCreateMode) {
|
if (!isCreateMode) {
|
||||||
final param = LoadSpaceDetailsParam(
|
final param = LoadSpaceDetailsParam(
|
||||||
spaceUuid: widget.spaceModel.uuid,
|
spaceUuid: widget.spaceModel.uuid,
|
||||||
communityUuid: widget.context
|
communityUuid: widget.communityUuid,
|
||||||
.read<CommunitiesTreeSelectionBloc>()
|
|
||||||
.state
|
|
||||||
.selectedCommunity!
|
|
||||||
.uuid,
|
|
||||||
);
|
);
|
||||||
widget.context.read<SpaceDetailsBloc>().add(LoadSpaceDetails(param));
|
widget.context.read<SpaceDetailsBloc>().add(LoadSpaceDetails(param));
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
|||||||
..._subspaces,
|
..._subspaces,
|
||||||
Subspace(
|
Subspace(
|
||||||
name: name,
|
name: name,
|
||||||
uuid: const Uuid().v4(),
|
uuid: '${const Uuid().v4()}-NewTag',
|
||||||
productAllocations: const [],
|
productAllocations: const [],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -3,41 +3,19 @@ import 'package:equatable/equatable.dart';
|
|||||||
class Tag extends Equatable {
|
class Tag extends Equatable {
|
||||||
final String uuid;
|
final String uuid;
|
||||||
final String name;
|
final String name;
|
||||||
final String createdAt;
|
|
||||||
final String updatedAt;
|
|
||||||
|
|
||||||
const Tag({
|
const Tag({
|
||||||
required this.uuid,
|
required this.uuid,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.createdAt,
|
|
||||||
required this.updatedAt,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Tag.empty() => const Tag(
|
|
||||||
uuid: '',
|
|
||||||
name: '',
|
|
||||||
createdAt: '',
|
|
||||||
updatedAt: '',
|
|
||||||
);
|
|
||||||
|
|
||||||
factory Tag.fromJson(Map<String, dynamic> json) {
|
factory Tag.fromJson(Map<String, dynamic> json) {
|
||||||
return Tag(
|
return Tag(
|
||||||
uuid: json['uuid'] as String,
|
uuid: json['uuid'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
createdAt: json['createdAt'] as String,
|
|
||||||
updatedAt: json['updatedAt'] as String,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'uuid': uuid,
|
|
||||||
'name': name,
|
|
||||||
'createdAt': createdAt,
|
|
||||||
'updatedAt': updatedAt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [uuid, name, createdAt, updatedAt];
|
List<Object?> get props => [uuid, name];
|
||||||
}
|
}
|
||||||
|
@ -214,9 +214,12 @@ class _AssignTagsDialogState extends State<AssignTagsDialog> {
|
|||||||
for (final product in newProducts) {
|
for (final product in newProducts) {
|
||||||
_space.productAllocations.add(
|
_space.productAllocations.add(
|
||||||
ProductAllocation(
|
ProductAllocation(
|
||||||
uuid: const Uuid().v4(),
|
uuid: '${const Uuid().v4()}-NewProductUuid',
|
||||||
product: product,
|
product: product,
|
||||||
tag: Tag.empty(),
|
tag: Tag(
|
||||||
|
uuid: '${const Uuid().v4()}-NewTag',
|
||||||
|
name: '',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class ProductTagField extends StatefulWidget {
|
class ProductTagField extends StatefulWidget {
|
||||||
final List<Tag> items;
|
final List<Tag> items;
|
||||||
@ -53,13 +54,8 @@ class _ProductTagFieldState extends State<ProductTagField> {
|
|||||||
void _submit(String value) {
|
void _submit(String value) {
|
||||||
final lowerCaseValue = value.toLowerCase();
|
final lowerCaseValue = value.toLowerCase();
|
||||||
final selectedTag = widget.items.firstWhere(
|
final selectedTag = widget.items.firstWhere(
|
||||||
(tag) => tag.name.toLowerCase() == lowerCaseValue,
|
(e) => e.name.toLowerCase() == lowerCaseValue,
|
||||||
orElse: () => Tag(
|
orElse: () => Tag(uuid: '${const Uuid().v4()}-NewTag', name: value),
|
||||||
name: value,
|
|
||||||
uuid: '',
|
|
||||||
createdAt: '',
|
|
||||||
updatedAt: '',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
widget.onSelected(selectedTag);
|
widget.onSelected(selectedTag);
|
||||||
_closeDropdown();
|
_closeDropdown();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart';
|
||||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
@ -12,17 +14,23 @@ class RemoteUpdateSpaceService implements UpdateSpaceService {
|
|||||||
static const _defaultErrorMessage = 'Failed to update space';
|
static const _defaultErrorMessage = 'Failed to update space';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SpaceDetailsModel> updateSpace(SpaceDetailsModel space) async {
|
Future<SpaceDetailsModel> updateSpace(UpdateSpaceParam param) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.put(
|
final path = await _makeUrl(param);
|
||||||
path: 'endpoint',
|
await _httpService.put(
|
||||||
body: space.toJson(),
|
path: path,
|
||||||
expectedResponseModel: (data) => SpaceDetailsModel.fromJson(
|
body: param.toJson(),
|
||||||
data as Map<String, dynamic>,
|
expectedResponseModel: (data) {
|
||||||
),
|
final response = data as Map<String, dynamic>;
|
||||||
|
final isSuccess = response['success'] as bool;
|
||||||
|
if (!isSuccess) {
|
||||||
|
throw APIException(response['error'] as String);
|
||||||
|
}
|
||||||
|
return isSuccess;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return response;
|
return param.space;
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
final message = e.response?.data as Map<String, dynamic>?;
|
final message = e.response?.data as Map<String, dynamic>?;
|
||||||
final error = message?['error'] as Map<String, dynamic>?;
|
final error = message?['error'] as Map<String, dynamic>?;
|
||||||
@ -37,4 +45,23 @@ class RemoteUpdateSpaceService implements UpdateSpaceService {
|
|||||||
throw APIException(formattedErrorMessage);
|
throw APIException(formattedErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> _makeUrl(UpdateSpaceParam param) async {
|
||||||
|
final projectUuid = await ProjectManager.getProjectUUID();
|
||||||
|
if (projectUuid == null || projectUuid.isEmpty) {
|
||||||
|
throw APIException('Project UUID is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
final spaceUuid = param.space.uuid;
|
||||||
|
if (spaceUuid.isEmpty) {
|
||||||
|
throw APIException('Space UUID is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
final communityUuid = param.communityUuid;
|
||||||
|
if (communityUuid.isEmpty) {
|
||||||
|
throw APIException('Community UUID is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/projects/$projectUuid/communities/$communityUuid/spaces/$spaceUuid';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
|
||||||
|
class UpdateSpaceParam {
|
||||||
|
UpdateSpaceParam({
|
||||||
|
required this.space,
|
||||||
|
required this.communityUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SpaceDetailsModel space;
|
||||||
|
final String communityUuid;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'spaceName': space.spaceName,
|
||||||
|
'icon': space.icon,
|
||||||
|
'subspaces': space.subspaces.map((e) => e._toJson()).toList(),
|
||||||
|
'productAllocations':
|
||||||
|
space.productAllocations.map((e) => e._toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _ProductAllocationToJson on ProductAllocation {
|
||||||
|
Map<String, dynamic> _toJson() {
|
||||||
|
final isNewTag = tag.uuid.isEmpty;
|
||||||
|
return <String, dynamic>{
|
||||||
|
if (isNewTag) 'tagName': tag.name else 'tagUuid': tag.uuid,
|
||||||
|
'productUuid': product.uuid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _SubspaceToJson on Subspace {
|
||||||
|
Map<String, dynamic> _toJson() {
|
||||||
|
final isNewSubspace = uuid.endsWith('-NewTag');
|
||||||
|
return <String, dynamic>{
|
||||||
|
if (!isNewSubspace) 'uuid': uuid,
|
||||||
|
'subspaceName': name,
|
||||||
|
'productAllocations': productAllocations.map((e) => e._toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart';
|
||||||
|
|
||||||
abstract class UpdateSpaceService {
|
abstract interface class UpdateSpaceService {
|
||||||
Future<SpaceDetailsModel> updateSpace(SpaceDetailsModel space);
|
Future<SpaceDetailsModel> updateSpace(UpdateSpaceParam param);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart';
|
||||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ class UpdateSpaceBloc extends Bloc<UpdateSpaceEvent, UpdateSpaceState> {
|
|||||||
) async {
|
) async {
|
||||||
emit(UpdateSpaceLoading());
|
emit(UpdateSpaceLoading());
|
||||||
try {
|
try {
|
||||||
final updatedSpace = await _updateSpaceService.updateSpace(event.space);
|
final updatedSpace = await _updateSpaceService.updateSpace(event.param);
|
||||||
emit(UpdateSpaceSuccess(updatedSpace));
|
emit(UpdateSpaceSuccess(updatedSpace));
|
||||||
} on APIException catch (e) {
|
} on APIException catch (e) {
|
||||||
emit(UpdateSpaceFailure(e.message));
|
emit(UpdateSpaceFailure(e.message));
|
||||||
|
@ -8,10 +8,10 @@ sealed class UpdateSpaceEvent extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class UpdateSpace extends UpdateSpaceEvent {
|
final class UpdateSpace extends UpdateSpaceEvent {
|
||||||
const UpdateSpace(this.space);
|
const UpdateSpace(this.param);
|
||||||
|
|
||||||
final SpaceDetailsModel space;
|
final UpdateSpaceParam param;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [space];
|
List<Object> get props => [param];
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,10 @@ final class UpdateSpaceSuccess extends UpdateSpaceState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class UpdateSpaceFailure extends UpdateSpaceState {
|
final class UpdateSpaceFailure extends UpdateSpaceState {
|
||||||
final String message;
|
final String errorMessage;
|
||||||
|
|
||||||
const UpdateSpaceFailure(this.message);
|
const UpdateSpaceFailure(this.errorMessage);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [message];
|
List<Object> get props => [errorMessage];
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,6 @@ abstract class ColorsManager {
|
|||||||
static const Color invitedOrange = Color(0xFFFFE193);
|
static const Color invitedOrange = Color(0xFFFFE193);
|
||||||
static const Color invitedOrangeText = Color(0xFFFFBF00);
|
static const Color invitedOrangeText = Color(0xFFFFBF00);
|
||||||
static const Color lightGrayBorderColor = Color(0xB2D5D5D5);
|
static const Color lightGrayBorderColor = Color(0xB2D5D5D5);
|
||||||
//background: #F8F8F8;
|
|
||||||
static const Color vividBlue = Color(0xFF023DFE);
|
static const Color vividBlue = Color(0xFF023DFE);
|
||||||
static const Color semiTransparentRed = Color(0x99FF0000);
|
static const Color semiTransparentRed = Color(0x99FF0000);
|
||||||
static const Color grey700 = Color(0xFF2D3748);
|
static const Color grey700 = Color(0xFF2D3748);
|
||||||
@ -85,4 +84,6 @@ abstract class ColorsManager {
|
|||||||
static const Color minBlueDot = Color(0xFF023DFE);
|
static const Color minBlueDot = Color(0xFF023DFE);
|
||||||
static const Color grey25 = Color(0xFFF9F9F9);
|
static const Color grey25 = Color(0xFFF9F9F9);
|
||||||
static const Color grey50 = Color(0xFF718096);
|
static const Color grey50 = Color(0xFF718096);
|
||||||
|
static const Color red100 = Color(0xFFFE0202);
|
||||||
|
static const Color grey800 = Color(0xffF8F8F8);
|
||||||
}
|
}
|
||||||
|
@ -141,4 +141,5 @@ abstract class ApiEndpoints {
|
|||||||
static const String saveSchedule = '/schedule/{deviceUuid}';
|
static const String saveSchedule = '/schedule/{deviceUuid}';
|
||||||
|
|
||||||
static const String getBookableSpaces = '/bookable-spaces';
|
static const String getBookableSpaces = '/bookable-spaces';
|
||||||
|
static const String getCalendarEvents = '/api';
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user