mirror of
https://github.com/SyncrowIOT/syncrow-app.git
synced 2026-10-23 10:27:56 +00:00
@@ -2,12 +2,17 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.GALLERY"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="syncrow_app"
|
android:label="syncrow_app"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false">
|
||||||
>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||||
|
'$(inherited)',
|
||||||
|
## dart: PermissionGroup.photos
|
||||||
|
'PERMISSION_PHOTOS=1',
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+13
-1
@@ -1,4 +1,6 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- device_info_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- Firebase/Analytics (10.20.0):
|
- Firebase/Analytics (10.20.0):
|
||||||
- Firebase/Core
|
- Firebase/Core
|
||||||
- Firebase/Core (10.20.0):
|
- Firebase/Core (10.20.0):
|
||||||
@@ -125,6 +127,8 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (7.13.3):
|
- GoogleUtilities/UserDefaults (7.13.3):
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
|
- image_picker_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- nanopb (2.30909.1):
|
- nanopb (2.30909.1):
|
||||||
- nanopb/decode (= 2.30909.1)
|
- nanopb/decode (= 2.30909.1)
|
||||||
- nanopb/encode (= 2.30909.1)
|
- nanopb/encode (= 2.30909.1)
|
||||||
@@ -199,12 +203,14 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_localization (from `.symlinks/plugins/flutter_localization/ios`)
|
- flutter_localization (from `.symlinks/plugins/flutter_localization/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- onesignal_flutter (from `.symlinks/plugins/onesignal_flutter/ios`)
|
- onesignal_flutter (from `.symlinks/plugins/onesignal_flutter/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
@@ -232,6 +238,8 @@ SPEC REPOS:
|
|||||||
- PromisesSwift
|
- PromisesSwift
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
device_info_plus:
|
||||||
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
firebase_analytics:
|
firebase_analytics:
|
||||||
:path: ".symlinks/plugins/firebase_analytics/ios"
|
:path: ".symlinks/plugins/firebase_analytics/ios"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
@@ -244,6 +252,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_localization/ios"
|
:path: ".symlinks/plugins/flutter_localization/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
image_picker_ios:
|
||||||
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
onesignal_flutter:
|
onesignal_flutter:
|
||||||
:path: ".symlinks/plugins/onesignal_flutter/ios"
|
:path: ".symlinks/plugins/onesignal_flutter/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@@ -260,6 +270,7 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||||
Firebase: 10c8cb12fb7ad2ae0c09ffc86cd9c1ab392a0031
|
Firebase: 10c8cb12fb7ad2ae0c09ffc86cd9c1ab392a0031
|
||||||
firebase_analytics: 2c1c3057d5da3bd3aab819f7e6ee153a4e46c59e
|
firebase_analytics: 2c1c3057d5da3bd3aab819f7e6ee153a4e46c59e
|
||||||
firebase_core: c8628c7ce80f79439149549052bff22f6784fbf5
|
firebase_core: c8628c7ce80f79439149549052bff22f6784fbf5
|
||||||
@@ -277,6 +288,7 @@ SPEC CHECKSUMS:
|
|||||||
GoogleAppMeasurement: bb3c564c3efb933136af0e94899e0a46167466a8
|
GoogleAppMeasurement: bb3c564c3efb933136af0e94899e0a46167466a8
|
||||||
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
|
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
|
||||||
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
|
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
|
||||||
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
|
nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
|
||||||
onesignal_flutter: 5ce68a29861960168e81101cb1bd685d264361de
|
onesignal_flutter: 5ce68a29861960168e81101cb1bd685d264361de
|
||||||
OneSignalXCFramework: bdf74fdc06888f9466dc21e826fe1549ed143095
|
OneSignalXCFramework: bdf74fdc06888f9466dc21e826fe1549ed143095
|
||||||
@@ -289,6 +301,6 @@ SPEC CHECKSUMS:
|
|||||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||||
|
|
||||||
PODFILE CHECKSUM: cf86fcba3fb3dbd505936bc190bb0b8fe3dd2498
|
PODFILE CHECKSUM: 4243bd7f9184f79552dd731a7c9d5cad03bd2706
|
||||||
|
|
||||||
COCOAPODS: 1.15.2
|
COCOAPODS: 1.15.2
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>We need access to your photo library to allow you to select and upload photos.</string>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import 'package:syncrow_app/features/scene/view/scene_view.dart';
|
|||||||
import 'package:syncrow_app/generated/assets.dart';
|
import 'package:syncrow_app/generated/assets.dart';
|
||||||
import 'package:syncrow_app/navigation/navigation_service.dart';
|
import 'package:syncrow_app/navigation/navigation_service.dart';
|
||||||
import 'package:syncrow_app/services/api/devices_api.dart';
|
import 'package:syncrow_app/services/api/devices_api.dart';
|
||||||
|
import 'package:syncrow_app/services/api/profile_api.dart';
|
||||||
import 'package:syncrow_app/services/api/spaces_api.dart';
|
import 'package:syncrow_app/services/api/spaces_api.dart';
|
||||||
import 'package:syncrow_app/utils/helpers/custom_page_route.dart';
|
import 'package:syncrow_app/utils/helpers/custom_page_route.dart';
|
||||||
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
|
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
|
||||||
@@ -33,6 +34,7 @@ part 'home_state.dart';
|
|||||||
class HomeCubit extends Cubit<HomeState> {
|
class HomeCubit extends Cubit<HomeState> {
|
||||||
HomeCubit._() : super(HomeInitial()) {
|
HomeCubit._() : super(HomeInitial()) {
|
||||||
checkIfNotificationPermissionGranted();
|
checkIfNotificationPermissionGranted();
|
||||||
|
fetchUserInfo();
|
||||||
if (selectedSpace == null) {
|
if (selectedSpace == null) {
|
||||||
fetchUnitsByUserId();
|
fetchUnitsByUserId();
|
||||||
// .then((value) {
|
// .then((value) {
|
||||||
@@ -42,7 +44,7 @@ class HomeCubit extends Cubit<HomeState> {
|
|||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static UserModel? user;
|
||||||
static HomeCubit? _instance;
|
static HomeCubit? _instance;
|
||||||
static HomeCubit getInstance() {
|
static HomeCubit getInstance() {
|
||||||
// If an instance already exists, return it
|
// If an instance already exists, return it
|
||||||
@@ -50,6 +52,18 @@ class HomeCubit extends Cubit<HomeState> {
|
|||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future fetchUserInfo() async {
|
||||||
|
try {
|
||||||
|
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
||||||
|
user = await ProfileApi().fetchUserInfo(uuid);
|
||||||
|
emit(HomeUserInfoLoaded(user!)); // Emit state after fetching user info
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void emitSafe(HomeState newState) {
|
void emitSafe(HomeState newState) {
|
||||||
final cubit = this;
|
final cubit = this;
|
||||||
if (!cubit.isClosed) {
|
if (!cubit.isClosed) {
|
||||||
|
|||||||
@@ -58,3 +58,9 @@ class RoomSelected extends HomeState {
|
|||||||
class RoomUnSelected extends HomeState {}
|
class RoomUnSelected extends HomeState {}
|
||||||
|
|
||||||
class NavChangePage extends HomeState {}
|
class NavChangePage extends HomeState {}
|
||||||
|
// Define new state classes
|
||||||
|
class HomeUserInfoLoaded extends HomeState {
|
||||||
|
final UserModel user;
|
||||||
|
|
||||||
|
HomeUserInfoLoaded(this.user);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
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:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
@@ -8,6 +9,7 @@ import 'package:syncrow_app/features/auth/model/user_model.dart';
|
|||||||
import 'package:syncrow_app/navigation/navigation_service.dart';
|
import 'package:syncrow_app/navigation/navigation_service.dart';
|
||||||
import 'package:syncrow_app/navigation/routing_constants.dart';
|
import 'package:syncrow_app/navigation/routing_constants.dart';
|
||||||
import 'package:syncrow_app/services/api/authentication_api.dart';
|
import 'package:syncrow_app/services/api/authentication_api.dart';
|
||||||
|
import 'package:syncrow_app/services/api/profile_api.dart';
|
||||||
import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart';
|
import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart';
|
||||||
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
|
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
|
||||||
import 'package:syncrow_app/utils/resource_manager/strings_manager.dart';
|
import 'package:syncrow_app/utils/resource_manager/strings_manager.dart';
|
||||||
@@ -179,12 +181,14 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
if (token.accessTokenIsNotEmpty) {
|
if (token.accessTokenIsNotEmpty) {
|
||||||
debugPrint('token: ${token.accessToken}');
|
debugPrint('token: ${token.accessToken}');
|
||||||
FlutterSecureStorage storage = const FlutterSecureStorage();
|
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||||
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
|
await storage.write(
|
||||||
|
key: Token.loginAccessTokenKey,
|
||||||
|
value: token.accessToken
|
||||||
|
);
|
||||||
const FlutterSecureStorage().write(
|
const FlutterSecureStorage().write(
|
||||||
key: UserModel.userUuidKey,
|
key: UserModel.userUuidKey,
|
||||||
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
value: Token.decodeToken(token.accessToken)['uuid'].toString()
|
||||||
|
);
|
||||||
user = UserModel.fromToken(token);
|
user = UserModel.fromToken(token);
|
||||||
emailController.clear();
|
emailController.clear();
|
||||||
passwordController.clear();
|
passwordController.clear();
|
||||||
@@ -278,8 +282,7 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
try {
|
try {
|
||||||
emit(AuthTokenLoading());
|
emit(AuthTokenLoading());
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
final firstLaunch =
|
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
|
||||||
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
|
|
||||||
|
|
||||||
if (firstLaunch) {
|
if (firstLaunch) {
|
||||||
storage.deleteAll();
|
storage.deleteAll();
|
||||||
@@ -312,6 +315,7 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sendToForgetPassword({required String password}) async {
|
sendToForgetPassword({required String password}) async {
|
||||||
try {
|
try {
|
||||||
emit(AuthForgetPassLoading());
|
emit(AuthForgetPassLoading());
|
||||||
@@ -321,4 +325,8 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
emit(AuthForgetPassError(message: 'Something went wrong'));
|
emit(AuthForgetPassError(message: 'Something went wrong'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +1,81 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:syncrow_app/features/auth/model/token.dart';
|
import 'package:syncrow_app/features/auth/model/token.dart';
|
||||||
|
|
||||||
class UserModel {
|
class UserModel {
|
||||||
static String userUuidKey = 'userUuid';
|
static String userUuidKey = 'userUuid';
|
||||||
final String? uuid;
|
final String? uuid;
|
||||||
final String? email;
|
final String? email;
|
||||||
final String? name;
|
final String? firstName;
|
||||||
final String? photoUrl;
|
final String? lastName;
|
||||||
|
final Uint8List? profilePicture;
|
||||||
final String? phoneNumber;
|
final String? phoneNumber;
|
||||||
|
|
||||||
final bool? isEmailVerified;
|
final bool? isEmailVerified;
|
||||||
|
final String? regionName;
|
||||||
|
final String? timeZone;
|
||||||
final bool? isAgreementAccepted;
|
final bool? isAgreementAccepted;
|
||||||
|
|
||||||
UserModel({
|
UserModel({
|
||||||
required this.uuid,
|
required this.uuid,
|
||||||
required this.email,
|
required this.email,
|
||||||
required this.name,
|
required this.firstName,
|
||||||
required this.photoUrl,
|
required this.lastName,
|
||||||
|
required this.profilePicture,
|
||||||
required this.phoneNumber,
|
required this.phoneNumber,
|
||||||
required this.isEmailVerified,
|
required this.isEmailVerified,
|
||||||
required this.isAgreementAccepted,
|
required this.isAgreementAccepted,
|
||||||
|
required this.regionName, // Add this line
|
||||||
|
required this.timeZone, // Add this line
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||||
return UserModel(
|
return UserModel(
|
||||||
uuid: json['id'],
|
uuid: json['uuid'],
|
||||||
email: json['email'],
|
email: json['email'],
|
||||||
name: json['name'],
|
firstName: json['firstName'],
|
||||||
photoUrl: json['photoUrl'],
|
lastName: json['lastName'],
|
||||||
|
profilePicture: UserModel.decodeBase64Image(json['profilePicture']),
|
||||||
phoneNumber: json['phoneNumber'],
|
phoneNumber: json['phoneNumber'],
|
||||||
isEmailVerified: json['isEmailVerified'],
|
isEmailVerified: json['isEmailVerified'],
|
||||||
isAgreementAccepted: json['isAgreementAccepted'],
|
isAgreementAccepted: json['isAgreementAccepted'],
|
||||||
|
regionName: json['region']?['regionName'], // Extract regionName
|
||||||
|
timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//uuid to json
|
//uuid to json
|
||||||
|
|
||||||
//from token
|
//from token
|
||||||
factory UserModel.fromToken(Token token) {
|
factory UserModel.fromToken(Token token) {
|
||||||
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
|
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
|
||||||
|
|
||||||
return UserModel(
|
return UserModel(
|
||||||
uuid: tempJson['uuid'].toString(),
|
uuid: tempJson['uuid'].toString(),
|
||||||
email: tempJson['email'],
|
email: tempJson['email'],
|
||||||
name: null,
|
lastName: tempJson['lastName'],
|
||||||
photoUrl: null,
|
firstName:tempJson['firstName'] ,
|
||||||
|
profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']),
|
||||||
phoneNumber: null,
|
phoneNumber: null,
|
||||||
isEmailVerified: null,
|
isEmailVerified: null,
|
||||||
isAgreementAccepted: null,
|
isAgreementAccepted: null,
|
||||||
|
regionName: tempJson['region']?['regionName'],
|
||||||
|
timeZone: tempJson['timezone']?['timeZoneOffset'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Uint8List? decodeBase64Image(String? base64String) {
|
||||||
|
if (base64String != null) {
|
||||||
|
return base64.decode(base64String);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'id': uuid,
|
'id': uuid,
|
||||||
'email': email,
|
'email': email,
|
||||||
'name': name,
|
'lastName': lastName,
|
||||||
'photoUrl': photoUrl,
|
'firstName': firstName,
|
||||||
|
'photoUrl': profilePicture,
|
||||||
'phoneNumber': phoneNumber,
|
'phoneNumber': phoneNumber,
|
||||||
'isEmailVerified': isEmailVerified,
|
'isEmailVerified': isEmailVerified,
|
||||||
'isAgreementAccepted': isAgreementAccepted,
|
'isAgreementAccepted': isAgreementAccepted,
|
||||||
|
|||||||
@@ -7,85 +7,6 @@ class MenuCubit extends Cubit<MenuState> {
|
|||||||
|
|
||||||
static MenuCubit of(context) => BlocProvider.of<MenuCubit>(context);
|
static MenuCubit of(context) => BlocProvider.of<MenuCubit>(context);
|
||||||
|
|
||||||
// List<MenuListModel> menuLists = [
|
String name = '';
|
||||||
// MenuListModel(
|
|
||||||
// label: 'Home Management',
|
|
||||||
// listItems: [
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Create a Home',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Join a Home',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Manage Your Home',
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// MenuListModel(
|
|
||||||
// label: 'General Settings',
|
|
||||||
// listItems: [
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Voice Assistant',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Temperature unit',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Touch tone on panel',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Language',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Network diagnosis',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Clear cache',
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// MenuListModel(
|
|
||||||
// label: 'Messages Center',
|
|
||||||
// listItems: [
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Alerts',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Messages',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'FAQs',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Help & Feedback',
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// MenuListModel(
|
|
||||||
// label: 'Security and Privacy',
|
|
||||||
// listItems: [
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Security',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'privacy',
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// MenuListModel(
|
|
||||||
// label: 'Legal Information',
|
|
||||||
// listItems: [
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'About',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'Privacy Policy',
|
|
||||||
// ),
|
|
||||||
// ListItemModel(
|
|
||||||
// label: 'User Agreement',
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,309 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/model/region_model.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/model/time_zone_model.dart';
|
||||||
|
import 'package:syncrow_app/services/api/profile_api.dart';
|
||||||
|
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
|
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||||
|
bool isSaving = false;
|
||||||
|
bool editName = false;
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
File? image;
|
||||||
|
final ImagePicker _picker = ImagePicker();
|
||||||
|
String timeZoneSelected = '';
|
||||||
|
String regionSelected = '';
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
final TextEditingController nameController = TextEditingController(
|
||||||
|
text: '${HomeCubit.user!.firstName} ${HomeCubit.user!.lastName}');
|
||||||
|
List<TimeZone>? timeZoneList;
|
||||||
|
List<RegionModel>? regionList;
|
||||||
|
|
||||||
|
|
||||||
|
ProfileBloc() : super(InitialState()) {
|
||||||
|
on<InitialProfileEvent>(_fetchUserInfo);
|
||||||
|
on<TimeZoneInitialEvent>(_fetchTimeZone);
|
||||||
|
on<RegionInitialEvent>(_fetchRegion);
|
||||||
|
on<SaveNameEvent>(saveName);
|
||||||
|
on<SelectImageEvent>(_selectImage);
|
||||||
|
on<ChangeNameEvent>(_changeName);
|
||||||
|
on<SelectTimeZoneEvent>(selectTimeZone);
|
||||||
|
on<SearchRegionEvent>(searchRegion);
|
||||||
|
on<SearchTimeZoneEvent>(searchTimeZone);
|
||||||
|
on<SelectRegionEvent>(selectRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveName(SaveNameEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
if (_validateInputs()) return;
|
||||||
|
try {
|
||||||
|
add(const ChangeNameEvent(value: false));
|
||||||
|
isSaving = true;
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
final fullName = nameController.text;
|
||||||
|
final nameParts = fullName.split(' ');
|
||||||
|
final firstName = nameParts[0];
|
||||||
|
final lastName = nameParts.length > 1 ? nameParts[1] : '';
|
||||||
|
var response = await ProfileApi.saveName(firstName: firstName, lastName: lastName);
|
||||||
|
add(InitialProfileEvent());
|
||||||
|
await HomeCubit.getInstance().fetchUserInfo();
|
||||||
|
CustomSnackBar.displaySnackBar('Save Successfully');
|
||||||
|
emit(SaveState());
|
||||||
|
} catch (e) {
|
||||||
|
emit(FailedState(errorMessage: e.toString()));
|
||||||
|
} finally {
|
||||||
|
isSaving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _changeName(ChangeNameEvent event, Emitter<ProfileState> emit) {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
editName = event.value!;
|
||||||
|
if (editName) {
|
||||||
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
|
focusNode.requestFocus();
|
||||||
|
});
|
||||||
|
}else {
|
||||||
|
focusNode.unfocus();
|
||||||
|
}
|
||||||
|
emit(NameEditingState(editName: editName));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fetchUserInfo(InitialProfileEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
try {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
HomeCubit.user = await ProfileApi().fetchUserInfo(HomeCubit.user!.uuid);
|
||||||
|
emit(SaveState());
|
||||||
|
} catch (e) {
|
||||||
|
emit(FailedState(errorMessage: e.toString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _fetchTimeZone(TimeZoneInitialEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
try {
|
||||||
|
timeZoneList = await ProfileApi.fetchTimeZone();
|
||||||
|
emit(UpdateState(timeZoneList: timeZoneList!));
|
||||||
|
return timeZoneList;
|
||||||
|
} catch (e) {
|
||||||
|
emit(FailedState(errorMessage: e.toString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future selectTimeZone(SelectTimeZoneEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
try {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
timeZoneSelected = event.val;
|
||||||
|
await ProfileApi.saveTimeZone(regionUuid: event.val);
|
||||||
|
CustomSnackBar.displaySnackBar('Save Successfully');
|
||||||
|
emit(SaveState());
|
||||||
|
} catch (e) {
|
||||||
|
emit(FailedState(errorMessage: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future selectRegion(SelectRegionEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
try {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
await ProfileApi.saveRegion(regionUuid:event.val );
|
||||||
|
CustomSnackBar.displaySnackBar('Save Successfully');
|
||||||
|
emit(SaveState());
|
||||||
|
} catch (e) {
|
||||||
|
emit(FailedState(errorMessage: e.toString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future searchRegion(SearchRegionEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
final query = event.query.toLowerCase();
|
||||||
|
if(event.query.isNotEmpty){
|
||||||
|
final filteredRegions = regionList?.where((region) {
|
||||||
|
return region.name.toLowerCase().contains(query);
|
||||||
|
}).toList() ?? [];
|
||||||
|
regionList = filteredRegions;
|
||||||
|
emit(RegionsLoadedState(regions: filteredRegions));
|
||||||
|
}else{
|
||||||
|
regionList = await ProfileApi.fetchRegion();
|
||||||
|
emit(RegionsLoadedState(regions: regionList!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future searchTimeZone(SearchTimeZoneEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
final query = event.query.toLowerCase();
|
||||||
|
if(event.query.isNotEmpty){
|
||||||
|
final filtered = timeZoneList?.where((region) {
|
||||||
|
return region.name.toLowerCase().contains(query);
|
||||||
|
}).toList() ?? [];
|
||||||
|
timeZoneList = filtered;
|
||||||
|
emit(TimeZoneLoadedState(regions: filtered));
|
||||||
|
}else{
|
||||||
|
timeZoneList = await ProfileApi.fetchTimeZone();
|
||||||
|
emit(UpdateState(timeZoneList: timeZoneList!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _fetchRegion(RegionInitialEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
try {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
regionList = await ProfileApi.fetchRegion();
|
||||||
|
emit(RegionsLoadedState(regions: regionList!));
|
||||||
|
} catch (e) {
|
||||||
|
emit(FailedState(errorMessage: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectImage(SelectImageEvent event, Emitter<ProfileState> emit) async {
|
||||||
|
if (await _requestPermission()) {
|
||||||
|
emit(ChangeImageState());
|
||||||
|
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||||
|
if (pickedFile != null) {
|
||||||
|
image = File(pickedFile.path);
|
||||||
|
final bytes = image!.readAsBytesSync().lengthInBytes;
|
||||||
|
final kb = bytes / 1024;
|
||||||
|
final mb = kb / 1024;
|
||||||
|
if(mb>1){
|
||||||
|
image=null;
|
||||||
|
CustomSnackBar.displaySnackBar('Image size must be 1 MB or less');
|
||||||
|
}else{
|
||||||
|
await _saveImage();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('No image selected.');
|
||||||
|
}
|
||||||
|
emit(ImageSelectedState());
|
||||||
|
} else {
|
||||||
|
_showPermissionDeniedDialog(event.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveImage() async {
|
||||||
|
emit(LoadingInitialState());
|
||||||
|
List<int> imageBytes = image!.readAsBytesSync();
|
||||||
|
String base64Image = base64Encode(imageBytes);
|
||||||
|
print(base64Image);
|
||||||
|
var response = await ProfileApi.saveImage(base64Image);
|
||||||
|
emit(ImageSelectedState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showPermissionDeniedDialog(BuildContext context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Permission Denied'),
|
||||||
|
content: const Text(
|
||||||
|
'Photo access is required to select an image. Please allow photo access in the app settings.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
openAppSettings();
|
||||||
|
},
|
||||||
|
child: const Text('Settings'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool _validateInputs() {
|
||||||
|
final nameError = fullNameValidator(nameController.text);
|
||||||
|
if (nameError != null) {
|
||||||
|
CustomSnackBar.displaySnackBar(nameError);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String? fullNameValidator(String? value) {
|
||||||
|
if (value == null) return 'Full name is required';
|
||||||
|
|
||||||
|
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
|
||||||
|
|
||||||
|
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
|
||||||
|
return 'Full name must be between 2 and 30 characters long';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if it contains anything but alphanumeric spaces and single quote
|
||||||
|
|
||||||
|
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
|
||||||
|
return 'Only alphanumeric characters, space, dash and single quote are allowed';
|
||||||
|
}
|
||||||
|
|
||||||
|
final parts = withoutExtraSpaces.split(' ');
|
||||||
|
|
||||||
|
if (parts.length < 2) return 'Full name must contain first and last names';
|
||||||
|
|
||||||
|
if (parts.length > 3) return 'Full name can at most contain 3 parts';
|
||||||
|
|
||||||
|
if (parts.any((part) => part.length < 2 || part.length > 30)) {
|
||||||
|
return 'Full name parts must be between 2 and 30 characters long';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<bool> _requestPermission() async {
|
||||||
|
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||||
|
if (Platform.isAndroid ) {
|
||||||
|
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||||
|
PermissionStatus status = await Permission.photos.status;
|
||||||
|
if(androidInfo.version.sdkInt<33){
|
||||||
|
if (status.isDenied) {
|
||||||
|
PermissionStatus status = await Permission.storage.request();
|
||||||
|
if (status.isGranted) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if (status.isGranted) {
|
||||||
|
return true;
|
||||||
|
} else if (status.isDenied) {
|
||||||
|
PermissionStatus status = await Permission.photos.request();
|
||||||
|
if (status.isGranted) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
bool firstClick = sharedPreferences.getBool('firstPermission') ?? true;
|
||||||
|
await sharedPreferences.setBool('firstPermission', false);
|
||||||
|
if (firstClick == false) {
|
||||||
|
var status = await Permission.photos.status;
|
||||||
|
return status.isGranted;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
abstract class ProfileEvent extends Equatable {
|
||||||
|
const ProfileEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class InitialProfileEvent extends ProfileEvent {}
|
||||||
|
|
||||||
|
class TimeZoneInitialEvent extends ProfileEvent {}
|
||||||
|
|
||||||
|
class ChangeNameEvent extends ProfileEvent {
|
||||||
|
final bool? value;
|
||||||
|
const ChangeNameEvent({ this.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
class RegionInitialEvent extends ProfileEvent {}
|
||||||
|
|
||||||
|
|
||||||
|
class SaveNameEvent extends ProfileEvent {
|
||||||
|
final BuildContext context;
|
||||||
|
const SaveNameEvent({required this.context});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [context];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SelectImageEvent extends ProfileEvent {
|
||||||
|
final BuildContext context;
|
||||||
|
final bool isSelected;
|
||||||
|
const SelectImageEvent({required this.context,required this.isSelected});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [context,isSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ToggleRepeatEvent extends ProfileEvent {}
|
||||||
|
|
||||||
|
class SelectTimeZoneEvent extends ProfileEvent {
|
||||||
|
final String val;
|
||||||
|
final BuildContext context;
|
||||||
|
const SelectTimeZoneEvent({required this.val,required this.context});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [val];
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectRegionEvent extends ProfileEvent {
|
||||||
|
final String val;
|
||||||
|
final BuildContext context;
|
||||||
|
const SelectRegionEvent({required this.val,required this.context});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [val,context];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SearchRegionEvent extends ProfileEvent {
|
||||||
|
final String query;
|
||||||
|
|
||||||
|
const SearchRegionEvent({required this.query});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [query];
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchTimeZoneEvent extends ProfileEvent {
|
||||||
|
final String query;
|
||||||
|
|
||||||
|
const SearchTimeZoneEvent({required this.query});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [query];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/model/region_model.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/model/time_zone_model.dart';
|
||||||
|
|
||||||
|
class ProfileState extends Equatable {
|
||||||
|
const ProfileState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class InitialState extends ProfileState {}
|
||||||
|
|
||||||
|
class LoadingInitialState extends ProfileState {}
|
||||||
|
|
||||||
|
class UpdateState extends ProfileState {
|
||||||
|
final List<TimeZone> timeZoneList;
|
||||||
|
|
||||||
|
UpdateState({required this.timeZoneList});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NameEditingState extends ProfileState {
|
||||||
|
final bool editName;
|
||||||
|
|
||||||
|
NameEditingState({required this.editName});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FailedState extends ProfileState {
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
const FailedState({required this.errorMessage});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [errorMessage];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ImageSelectedState extends ProfileState {}
|
||||||
|
|
||||||
|
class ChangeImageState extends ProfileState {}
|
||||||
|
|
||||||
|
class SaveState extends ProfileState {}
|
||||||
|
|
||||||
|
class LoadingSaveState extends ProfileState {}
|
||||||
|
class RegionsLoadedState extends ProfileState {
|
||||||
|
final List<RegionModel> regions;
|
||||||
|
|
||||||
|
const RegionsLoadedState({required this.regions});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeZoneLoadedState extends ProfileState {
|
||||||
|
final List<TimeZone> regions;
|
||||||
|
|
||||||
|
const TimeZoneLoadedState({required this.regions});
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class RegionModel {
|
||||||
|
final String name;
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
RegionModel({
|
||||||
|
required this.name,
|
||||||
|
required this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RegionModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RegionModel(
|
||||||
|
name: json['regionName'],
|
||||||
|
id: json['uuid'].toString(), // Ensure id is a String
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'regionName': name,
|
||||||
|
'uuid': id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
class TimeZone {
|
||||||
|
final String name;
|
||||||
|
final String offset;
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
TimeZone({
|
||||||
|
required this.name,
|
||||||
|
required this.offset,
|
||||||
|
required this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory TimeZone.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TimeZone(
|
||||||
|
name: json['cityName'],
|
||||||
|
offset: json['timeZoneOffset'],
|
||||||
|
id: json['uuid'].toString(), // Ensure id is a String
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'name': name,
|
||||||
|
'offset': offset,
|
||||||
|
'id': id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,11 +21,12 @@ class MenuView extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return BlocBuilder<AuthCubit, AuthState>(
|
return BlocBuilder<AuthCubit, AuthState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final profileBloc = BlocProvider.of<MenuCubit>(context);
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const ProfileTab(),
|
ProfileTab(),
|
||||||
for (var section in menuSections)
|
for (var section in menuSections)
|
||||||
MenuList(
|
MenuList(
|
||||||
section: section,
|
section: section,
|
||||||
|
|||||||
@@ -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:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
|
|
||||||
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart';
|
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart';
|
||||||
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_event.dart';
|
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_event.dart';
|
||||||
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart';
|
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart';
|
||||||
@@ -11,8 +10,6 @@ import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart
|
|||||||
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
|
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
|
||||||
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
|
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
|
||||||
import 'package:syncrow_app/generated/assets.dart';
|
import 'package:syncrow_app/generated/assets.dart';
|
||||||
import 'package:syncrow_app/navigation/navigation_service.dart';
|
|
||||||
import 'package:syncrow_app/navigation/routing_constants.dart';
|
|
||||||
import 'package:syncrow_app/utils/context_extension.dart';
|
import 'package:syncrow_app/utils/context_extension.dart';
|
||||||
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
|
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
|
||||||
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
||||||
|
|||||||
@@ -1,43 +1,65 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
|
||||||
import 'package:syncrow_app/features/menu/view/widgets/profile/profile_view.dart';
|
import 'package:syncrow_app/features/menu/view/widgets/profile/profile_view.dart';
|
||||||
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
|
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
|
||||||
import 'package:syncrow_app/features/shared_widgets/syncrow_logo.dart';
|
|
||||||
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
|
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
|
||||||
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
|
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
|
||||||
|
|
||||||
class ProfileTab extends StatelessWidget {
|
class ProfileTab extends StatelessWidget {
|
||||||
const ProfileTab({
|
const ProfileTab({super.key,});
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<HomeCubit, HomeState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return _buildProfileContent(context );
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProfileContent(BuildContext context) {
|
||||||
|
final homeCubit = context.read<HomeCubit>();
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10,),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context)
|
||||||
|
.push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const ProfileView(),
|
builder: (context) => const ProfileView(),
|
||||||
),
|
),
|
||||||
);
|
).then((result) {
|
||||||
|
context.read<HomeCubit>().fetchUserInfo();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child: const Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
DefaultContainer(
|
DefaultContainer(
|
||||||
child: Column(
|
child: Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: const EdgeInsets.all(10.0),
|
||||||
children: [
|
child: Column(
|
||||||
BodyMedium(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
text: "Karim",
|
children: [
|
||||||
fontWeight: FontWeight.bold,
|
Row(
|
||||||
),
|
children: [
|
||||||
BodySmall(text: "Syncrow Account")
|
BodyMedium(
|
||||||
],
|
text: '${HomeCubit.user!.firstName ?? ''} ',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
BodyMedium(
|
||||||
|
text: HomeCubit.user!.lastName ?? '',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 5,),
|
||||||
|
const BodySmall(text: "Syncrow Account"),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -51,7 +73,16 @@ class ProfileTab extends StatelessWidget {
|
|||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 37,
|
radius: 37,
|
||||||
backgroundColor: Colors.grey,
|
backgroundColor: Colors.grey,
|
||||||
child: SyncrowLogo(),
|
child: ClipOval(
|
||||||
|
child: HomeCubit.user?.profilePicture != null
|
||||||
|
? Image.memory(
|
||||||
|
HomeCubit.user!.profilePicture!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
width: 110,
|
||||||
|
height: 110,
|
||||||
|
)
|
||||||
|
: Icon(Icons.person, size: 70), // Fallback if no image
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/view/widgets/profile/region_page.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/view/widgets/profile/time_zone_screen_page.dart';
|
||||||
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
|
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
|
||||||
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
|
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
|
||||||
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
|
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
|
||||||
import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart';
|
|
||||||
import 'package:syncrow_app/utils/context_extension.dart';
|
|
||||||
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
||||||
|
|
||||||
class ProfileView extends StatelessWidget {
|
class ProfileView extends StatelessWidget {
|
||||||
@@ -11,118 +17,185 @@ class ProfileView extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DefaultScaffold(
|
|
||||||
title: 'Profile Page',
|
return BlocProvider(
|
||||||
child: Column(
|
create: (BuildContext context) => ProfileBloc()..add(InitialProfileEvent()),
|
||||||
children: [
|
child: BlocConsumer<ProfileBloc, ProfileState>(
|
||||||
//profile pic
|
listener: (context, state) {},
|
||||||
const SizedBox.square(
|
builder: (context, state) {
|
||||||
dimension: 120,
|
final profileBloc = BlocProvider.of<ProfileBloc>(context);
|
||||||
child: CircleAvatar(
|
return DefaultScaffold(
|
||||||
backgroundColor: Colors.white,
|
title: 'Syncrow Account',
|
||||||
child: SizedBox.square(
|
child:
|
||||||
dimension: 115,
|
state is LoadingInitialState
|
||||||
child: CircleAvatar(
|
? const Center(child: CircularProgressIndicator()):
|
||||||
backgroundColor: Colors.grey,
|
Column(
|
||||||
child: FlutterLogo(),
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
),
|
),
|
||||||
),
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
profileBloc.add(SelectImageEvent(context: context, isSelected: false));
|
||||||
|
},
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: 125,
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: 120,
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
backgroundImage: profileBloc.image == null
|
||||||
|
? null
|
||||||
|
: FileImage(profileBloc.image!),
|
||||||
|
child: profileBloc.image != null
|
||||||
|
? null
|
||||||
|
:HomeCubit.user!.profilePicture != null
|
||||||
|
? ClipOval(
|
||||||
|
child: Image.memory(
|
||||||
|
HomeCubit.user!.profilePicture!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
SizedBox(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IntrinsicWidth(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
|
child: TextFormField(
|
||||||
|
maxLength: 30,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
focusNode: profileBloc.focusNode,
|
||||||
|
controller: profileBloc.nameController,
|
||||||
|
enabled: profileBloc.editName,
|
||||||
|
onEditingComplete: () {
|
||||||
|
profileBloc.add(SaveNameEvent(context: context));
|
||||||
|
},
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: "Your Name",
|
||||||
|
border: InputBorder.none,
|
||||||
|
fillColor: Colors.white10,
|
||||||
|
counterText: '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
profileBloc.add(const ChangeNameEvent(value: true));
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: Icon(
|
||||||
|
Icons.edit_outlined,
|
||||||
|
size: 20,
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
DefaultContainer(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 25,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const BodyMedium(text: 'email '),
|
||||||
|
Flexible(child: BodyMedium(text: HomeCubit.user!.email ?? 'No Email')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 1,
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
),
|
||||||
|
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const RegionPage(),
|
||||||
|
),
|
||||||
|
).then((result) {
|
||||||
|
profileBloc.add(InitialProfileEvent());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const BodyMedium(text: 'Region '),
|
||||||
|
Flexible(child: BodyMedium(text: HomeCubit.user!.regionName ?? 'No Region')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 1,
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const TimeZoneScreenPage(),
|
||||||
|
),
|
||||||
|
).then((result) {
|
||||||
|
profileBloc.add(InitialProfileEvent());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 15, bottom: 15),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const BodyMedium(text: 'Time Zone '),
|
||||||
|
Flexible(
|
||||||
|
child: BodyMedium(text: HomeCubit.user!.timeZone ?? "No Time Zone"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
const SizedBox(height: 20),
|
},
|
||||||
//name
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const TitleMedium(text: 'Karim'),
|
|
||||||
const SizedBox(
|
|
||||||
width: 5,
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
//TODO: Implement edit name
|
|
||||||
},
|
|
||||||
child: const Icon(
|
|
||||||
Icons.edit_outlined,
|
|
||||||
size: 20,
|
|
||||||
color: ColorsManager.textPrimaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
//Info
|
|
||||||
DefaultContainer(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 25,
|
|
||||||
vertical: 5,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const BodyMedium(text: 'Email '),
|
|
||||||
Flexible(
|
|
||||||
child: TextField(
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: ' [email protected]',
|
|
||||||
hintStyle:
|
|
||||||
context.bodyMedium.copyWith(color: Colors.grey),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 1,
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const BodyMedium(text: 'Region '),
|
|
||||||
Flexible(
|
|
||||||
child: TextField(
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'United Arab Emirates',
|
|
||||||
hintStyle:
|
|
||||||
context.bodyMedium.copyWith(color: Colors.grey),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 1,
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const BodyMedium(text: 'Time Zone '),
|
|
||||||
Flexible(
|
|
||||||
child: TextField(
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'GMT +4',
|
|
||||||
hintStyle:
|
|
||||||
context.bodyMedium.copyWith(color: Colors.grey),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart';
|
||||||
|
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
|
||||||
|
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
|
||||||
|
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
||||||
|
|
||||||
|
class RegionPage extends StatelessWidget {
|
||||||
|
const RegionPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (BuildContext context) => ProfileBloc()..add(RegionInitialEvent()),
|
||||||
|
child: BlocConsumer<ProfileBloc, ProfileState>(listener: (context, state) {
|
||||||
|
if (state is FailedState) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.errorMessage),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (state is SaveState) {
|
||||||
|
new Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
final profileBloc = BlocProvider.of<ProfileBloc>(context);
|
||||||
|
final regionList = profileBloc.regionList ?? []; // Safeguard against null
|
||||||
|
return DefaultScaffold(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
title: 'Region',
|
||||||
|
child: state is LoadingInitialState
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller:profileBloc.searchController ,
|
||||||
|
onChanged: (value) {
|
||||||
|
profileBloc.add(SearchRegionEvent(query: value));
|
||||||
|
},
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.search),
|
||||||
|
hintText: 'Search',
|
||||||
|
fillColor: ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: ColorsManager.onPrimaryColor,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20),
|
||||||
|
topRight: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child:Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10.0,top: 10.0,left: 15,right: 15),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: regionList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
profileBloc.add(SelectRegionEvent(val: regionList[index].id, context: context));
|
||||||
|
},
|
||||||
|
child:Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10,),
|
||||||
|
child: SizedBox(
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
leading: BodyMedium(
|
||||||
|
fontSize: 15,
|
||||||
|
text: regionList[index].name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(color: ColorsManager.textGray), // Divider between items
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart';
|
||||||
|
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
|
||||||
|
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
|
||||||
|
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
||||||
|
|
||||||
|
class TimeZoneScreenPage extends StatelessWidget {
|
||||||
|
const TimeZoneScreenPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (BuildContext context) => ProfileBloc()..add(TimeZoneInitialEvent()),
|
||||||
|
child:
|
||||||
|
BlocConsumer<ProfileBloc, ProfileState>(listener: (context, state) {
|
||||||
|
if (state is FailedState) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.errorMessage),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}if (state is SaveState) {
|
||||||
|
new Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
final profileBloc = BlocProvider.of<ProfileBloc>(context);
|
||||||
|
final timeZoneList = profileBloc.timeZoneList ?? []; // Safeguard against null
|
||||||
|
return DefaultScaffold(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
title: 'Time Zone',
|
||||||
|
child: state is LoadingInitialState
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller:profileBloc.searchController ,
|
||||||
|
onChanged: (value) {
|
||||||
|
profileBloc.add(SearchTimeZoneEvent(query: value));
|
||||||
|
},
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.search),
|
||||||
|
hintText: 'Search',
|
||||||
|
fillColor: ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: ColorsManager.onPrimaryColor,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20),
|
||||||
|
topRight: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10.0,top: 10.0,left: 15,right: 15),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: timeZoneList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
profileBloc.add(SelectTimeZoneEvent(val: timeZoneList[index].id, context: context));
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10,),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 45,
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
trailing: BodyMedium(
|
||||||
|
text: timeZoneList[index].offset,
|
||||||
|
fontSize: 13,
|
||||||
|
fontColor: ColorsManager.textGray,),
|
||||||
|
leading: BodyMedium(
|
||||||
|
fontSize: 15,
|
||||||
|
text: timeZoneList[index].name,),),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(color: ColorsManager.textGray), // Divider between items
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
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_app/features/auth/bloc/auth_cubit.dart';
|
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart';
|
||||||
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
|
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
|
||||||
import 'package:syncrow_app/navigation/navigation_service.dart';
|
import 'package:syncrow_app/navigation/navigation_service.dart';
|
||||||
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
||||||
@@ -26,6 +27,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider(create: (context) => AuthCubit()),
|
BlocProvider(create: (context) => AuthCubit()),
|
||||||
BlocProvider(create: (context) => CreateSceneBloc()),
|
BlocProvider(create: (context) => CreateSceneBloc()),
|
||||||
BlocProvider(create: (context) => SceneBloc()),
|
BlocProvider(create: (context) => SceneBloc()),
|
||||||
|
BlocProvider(create: (context) => ProfileBloc()),
|
||||||
|
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
|
|||||||
@@ -150,4 +150,14 @@ abstract class ApiEndpoints {
|
|||||||
//multiple-time offline
|
//multiple-time offline
|
||||||
static const String deleteTemporaryPassword =
|
static const String deleteTemporaryPassword =
|
||||||
'/door-lock/temporary-password/{doorLockUuid}/{passwordId}';
|
'/door-lock/temporary-password/{doorLockUuid}/{passwordId}';
|
||||||
|
|
||||||
|
//user
|
||||||
|
|
||||||
|
static const String getUser = '/user/{userUuid}';
|
||||||
|
static const String saveRegion = '/user/region/{userUuid}';
|
||||||
|
static const String saveTimeZone = '/user/timezone/{userUuid}';
|
||||||
|
static const String saveName = '/user/name/{userUuid}';
|
||||||
|
static const String sendPicture = '/user/profile-picture/{userUuid}';
|
||||||
|
static const String getRegion = '/region';
|
||||||
|
static const String getTimezone = '/timezone';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
|
||||||
|
import 'package:syncrow_app/features/auth/model/user_model.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/model/region_model.dart';
|
||||||
|
import 'package:syncrow_app/features/menu/model/time_zone_model.dart';
|
||||||
|
import 'package:syncrow_app/services/api/api_links_endpoints.dart';
|
||||||
|
import 'package:syncrow_app/services/api/http_service.dart';
|
||||||
|
|
||||||
|
class ProfileApi {
|
||||||
|
static final HTTPService _httpService = HTTPService();
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> saveName({String? firstName, String? lastName,}) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.put(
|
||||||
|
path: ApiEndpoints.saveName.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
|
||||||
|
body: {
|
||||||
|
"firstName": firstName,
|
||||||
|
"lastName": lastName
|
||||||
|
},
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return json;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future saveRegion({String? regionUuid,}) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.put(
|
||||||
|
path: ApiEndpoints.saveRegion.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
|
||||||
|
body: {
|
||||||
|
"regionUuid": regionUuid,
|
||||||
|
},
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return json;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future saveTimeZone({String? regionUuid,}) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.put(
|
||||||
|
path: ApiEndpoints.saveTimeZone.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
|
||||||
|
body: {
|
||||||
|
"timezoneUuid": regionUuid,
|
||||||
|
},
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return json;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> saveImage(String image) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.put(
|
||||||
|
path: ApiEndpoints.sendPicture.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
|
||||||
|
body: {
|
||||||
|
"profilePicture": 'data:image/png;base64,$image'
|
||||||
|
},
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return json;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future fetchUserInfo(userId) async {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!),
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return UserModel.fromJson(json);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<RegionModel>> fetchRegion() async {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: ApiEndpoints.getRegion,
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response as List<RegionModel>;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<TimeZone>> fetchTimeZone() async {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: ApiEndpoints.getTimezone,
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return (json as List).map((zone) => TimeZone.fromJson(zone)).toList();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response as List<TimeZone>;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+130
-2
@@ -129,6 +129,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
device_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: device_info_plus
|
||||||
|
sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.1.0"
|
||||||
|
device_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_info_plus_platform_interface
|
||||||
|
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -169,6 +185,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.0"
|
||||||
|
file_selector_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_linux
|
||||||
|
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.2+1"
|
||||||
|
file_selector_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_macos
|
||||||
|
sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.4"
|
||||||
|
file_selector_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_platform_interface
|
||||||
|
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.2"
|
||||||
|
file_selector_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_windows
|
||||||
|
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+1"
|
||||||
firebase_analytics:
|
firebase_analytics:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -307,6 +355,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.20"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -405,6 +461,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
image_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_picker
|
||||||
|
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
image_picker_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_android
|
||||||
|
sha256: "4161e1f843d8480d2e9025ee22411778c3c9eb7e40076dcf2da23d8242b7b51c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.12+3"
|
||||||
|
image_picker_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_for_web
|
||||||
|
sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.4"
|
||||||
|
image_picker_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_ios
|
||||||
|
sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.12"
|
||||||
|
image_picker_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_linux
|
||||||
|
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
|
image_picker_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_macos
|
||||||
|
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
|
image_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_platform_interface
|
||||||
|
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.10.0"
|
||||||
|
image_picker_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_windows
|
||||||
|
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -986,6 +1106,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.0"
|
version: "5.2.0"
|
||||||
|
win32_registry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32_registry
|
||||||
|
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1003,5 +1131,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.0"
|
version: "6.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.3.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.19.0"
|
flutter: ">=3.22.0"
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ dependencies:
|
|||||||
smooth_page_indicator: ^1.1.0
|
smooth_page_indicator: ^1.1.0
|
||||||
uuid: ^4.4.0
|
uuid: ^4.4.0
|
||||||
time_picker_spinner: ^1.0.0
|
time_picker_spinner: ^1.0.0
|
||||||
|
image_picker: ^1.1.2
|
||||||
|
device_info_plus: ^10.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^3.0.1
|
flutter_lints: ^3.0.1
|
||||||
|
|||||||
Reference in New Issue
Block a user