Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8ae3a48
Update smart_workspace.db
Art-Invis Apr 1, 2026
5bf679b
Add flutter-bloc dependencies to pubsec
Art-Invis Apr 3, 2026
47b3111
Setup BLoC architecture and refactor authentication
Art-Invis Apr 3, 2026
e177b61
Refactor: migrate dashboard and device CRUD logic to Cubit
Art-Invis Apr 3, 2026
6d15b7f
Refactor: move home schematic and manual control to stateless widgets
Art-Invis Apr 3, 2026
a7fa260
Refactor: migrate profile and MQTT sections to state management
Art-Invis Apr 3, 2026
8312c49
Update main wrapper and widget tests for BLoC, add some widgets
Art-Invis Apr 3, 2026
61330c1
Update gitignore
Art-Invis Apr 3, 2026
75af5f6
Style: reorganize project structure and group widgets by feature
Art-Invis Apr 3, 2026
d8db018
Fix: update widget tests for MqttCubit and completely remove provider…
Art-Invis Apr 3, 2026
f22e34b
Add iot_flashlight plugin dependency via git
Art-Invis Apr 4, 2026
d26d8bf
Add UI components for hardware override and platform warnings
Art-Invis Apr 4, 2026
5df3102
Implement emergency override logic with tap sequence and logs
Art-Invis Apr 4, 2026
57c7356
Integrate emergency override wrapper into profile screen
Art-Invis Apr 4, 2026
4770a72
Add BLoC layer as an advanced alternative state management
Art-Invis Apr 6, 2026
1482134
Consolidate production logic in Cubits for core functionality
Art-Invis Apr 6, 2026
34e1d69
Implement dual-architecture screens (Cubit active, BLoC documented)
Art-Invis Apr 6, 2026
29b3d14
Merge branch 'Laba-6' of https://github.com/Art-Invis/mobile_flutter_…
Art-Invis Apr 6, 2026
6eb3251
Implement dual-architecture to main.dart (Cubit active, BLoC documented)
Art-Invis Apr 6, 2026
e788790
Update interactive components with documented BLoC events
Art-Invis Apr 6, 2026
d5aa225
Fix to dart format
Art-Invis Apr 6, 2026
552fb02
Merge branch 'Laba-6' into Lab-7
Art-Invis Apr 6, 2026
270150f
Implement FlashlightCubit for accelerometer and hardware state manage…
Art-Invis Apr 7, 2026
b3558fc
Integrate shake trigger into Login screen
Art-Invis Apr 7, 2026
930f5b9
Add .gitattributes
Art-Invis Apr 7, 2026
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 .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web/** linguist-generated=true
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