Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
/instance
1 change: 1 addition & 0 deletions AzuriteConfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"instaceID":"3263f8fd-b41b-4b62-b68c-71c637aec757"}
Binary file modified instance/smart_workspace.db
Binary file not shown.
60 changes: 60 additions & 0 deletions lib/blocs/add_device/add_device_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mobile_flutter_iot/blocs/add_device/add_device_event.dart';
import 'package:mobile_flutter_iot/blocs/add_device/add_device_state.dart';
import 'package:mobile_flutter_iot/services/api_service.dart';

class AddDeviceBloc extends Bloc<AddDeviceEvent, AddDeviceState> {
final ApiService apiService;

AddDeviceBloc({
required this.apiService,
required Color initialColor,
required IconData initialIcon,
}) : super(
AddDeviceState(
selectedColor: initialColor,
selectedIcon: initialIcon,
),
) {
on<AddDeviceColorChanged>(
(event, emit) => emit(state.copyWith(selectedColor: event.color)),
);
on<AddDeviceIconChanged>(
(event, emit) => emit(state.copyWith(selectedIcon: event.icon)),
);
on<AddDeviceSaveRequested>(_onSaveRequested);
}

Future<void> _onSaveRequested(
AddDeviceSaveRequested event,
Emitter<AddDeviceState> emit,
) async {
emit(state.copyWith(isLoading: true));
try {
final success = event.isNew
? await apiService.addDevice(event.device)
: await apiService.updateDevice(event.device);

if (success) {
emit(state.copyWith(isLoading: false, isSuccess: true));
} else {
emit(
state.copyWith(
isLoading: false,
isSuccess: false,
errorMessage: 'API Error: Saved to local cache only.',
),
);
}
} catch (e) {
emit(
state.copyWith(
isLoading: false,
isSuccess: false,
errorMessage: 'Connection failed.',
),
);
}
}
}
32 changes: 32 additions & 0 deletions lib/blocs/add_device/add_device_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:mobile_flutter_iot/models/device_model.dart';

abstract class AddDeviceEvent extends Equatable {
const AddDeviceEvent();

@override
List<Object?> get props => [];
}

class AddDeviceColorChanged extends AddDeviceEvent {
final Color color;
const AddDeviceColorChanged(this.color);
@override
List<Object?> get props => [color];
}

class AddDeviceIconChanged extends AddDeviceEvent {
final IconData icon;
const AddDeviceIconChanged(this.icon);
@override
List<Object?> get props => [icon];
}

class AddDeviceSaveRequested extends AddDeviceEvent {
final DeviceModel device;
final bool isNew;
const AddDeviceSaveRequested({required this.device, required this.isNew});
@override
List<Object?> get props => [device, isNew];
}
38 changes: 38 additions & 0 deletions lib/blocs/add_device/add_device_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';

class AddDeviceState extends Equatable {
final Color selectedColor;
final IconData selectedIcon;
final bool isLoading;
final bool? isSuccess;
final String? errorMessage;

const AddDeviceState({
required this.selectedColor,
required this.selectedIcon,
this.isLoading = false,
this.isSuccess,
this.errorMessage,
});

AddDeviceState copyWith({
Color? selectedColor,
IconData? selectedIcon,
bool? isLoading,
bool? isSuccess,
String? errorMessage,
}) {
return AddDeviceState(
selectedColor: selectedColor ?? this.selectedColor,
selectedIcon: selectedIcon ?? this.selectedIcon,
isLoading: isLoading ?? this.isLoading,
isSuccess: isSuccess,
errorMessage: errorMessage,
);
}

@override
List<Object?> get props =>
[selectedColor, selectedIcon, isLoading, isSuccess, errorMessage];
}
128 changes: 128 additions & 0 deletions lib/blocs/auth/auth_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mobile_flutter_iot/blocs/auth/auth_event.dart';
import 'package:mobile_flutter_iot/blocs/auth/auth_state.dart';
import 'package:mobile_flutter_iot/models/user_model.dart';
import 'package:mobile_flutter_iot/repository/local_user_repository.dart';
import 'package:mobile_flutter_iot/services/api_service.dart';

class AuthBloc extends Bloc<AuthEvent, AuthState> {
final ApiService apiService;
final LocalUserRepository userRepository;
final _storage = const FlutterSecureStorage();

AuthBloc({
required this.apiService,
required this.userRepository,
}) : super(AuthInitial()) {
on<AuthCheckRequested>(_onCheckRequested);
on<AuthLoginRequested>(_onLoginRequested);
on<AuthRegisterRequested>(_onRegisterRequested);
on<AuthLogoutRequested>(_onLogoutRequested);
}

Future<void> _onCheckRequested(
AuthCheckRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
try {
final token = await _storage.read(key: 'access_token');
if (token != null) {
emit(AuthAuthenticated());
} else {
emit(AuthUnauthenticated());
}
} catch (_) {
emit(AuthUnauthenticated());
}
}

Future<void> _onLoginRequested(
AuthLoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
try {
final result = await apiService.login(event.email, event.password);

if (result != null && result['token'] != null) {
final token = result['token'].toString();
await _storage.write(key: 'access_token', value: token);

if (result['user'] != null) {
final userData = result['user'] as Map<String, dynamic>;

final user = UserModel(
fullName: userData['fullName']?.toString() ?? 'Unknown',
email: userData['email']?.toString() ?? event.email,
password: event.password, // Беремо з івенту
department: userData['department']?.toString() ?? 'IoT',
);
await userRepository.saveUser(user);
}

emit(AuthAuthenticated());
return;
}
} catch (e) {
//
}

final localUser = await userRepository.getUser();

if (localUser != null &&
localUser.email == event.email &&
localUser.password == event.password) {
await _storage.write(key: 'access_token', value: 'offline_mode_token');
emit(AuthAuthenticated());
} else {
emit(
const AuthError(
'Login Failed: Invalid credentials or Server Offline',
),
);
emit(AuthUnauthenticated());
}
}

Future<void> _onRegisterRequested(
AuthRegisterRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());

final userToSave = UserModel(
fullName: event.name,
email: event.email,
password: event.password,
department: 'IoT',
);

try {
final success = await apiService.register(userToSave);

if (success) {
add(AuthLoginRequested(email: event.email, password: event.password));
} else {
emit(const AuthError('Помилка реєстрації. Можливо, email вже існує.'));
}
} catch (e) {
await userRepository.saveUser(userToSave);
await _storage.write(key: 'access_token', value: 'offline_mode_token');
emit(AuthAuthenticated());
}
}

Future<void> _onLogoutRequested(
AuthLogoutRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());

await _storage.delete(key: 'access_token');
await userRepository.deleteUser();

emit(AuthUnauthenticated());
}
}
37 changes: 37 additions & 0 deletions lib/blocs/auth/auth_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';

abstract class AuthEvent extends Equatable {
const AuthEvent();

@override
List<Object?> get props => [];
}

class AuthCheckRequested extends AuthEvent {}

class AuthLoginRequested extends AuthEvent {
final String email;
final String password;

const AuthLoginRequested({required this.email, required this.password});

@override
List<Object?> get props => [email, password];
}

class AuthRegisterRequested extends AuthEvent {
final String email;
final String password;
final String name;

const AuthRegisterRequested({
required this.email,
required this.password,
required this.name,
});

@override
List<Object?> get props => [email, password, name];
}

class AuthLogoutRequested extends AuthEvent {}
25 changes: 25 additions & 0 deletions lib/blocs/auth/auth_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:equatable/equatable.dart';

abstract class AuthState extends Equatable {
const AuthState();

@override
List<Object?> get props => [];
}

class AuthInitial extends AuthState {}

class AuthLoading extends AuthState {}

class AuthAuthenticated extends AuthState {}

class AuthUnauthenticated extends AuthState {}

class AuthError extends AuthState {
final String message;

const AuthError(this.message);

@override
List<Object?> get props => [message];
}
Loading
Loading