Начальный коммит

This commit is contained in:
2026-01-30 21:54:00 +07:00
parent 51de113db5
commit 3881248187
81 changed files with 5424 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
// lib/services/api/api_service.dart
import 'package:flutter/material.dart';
import 'package:Stocky/models/task_adapter.dart';
import 'package:Stocky/services/local/StorageService.dart';
import 'package:Stocky/utils/constants.dart';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
class ApiService extends GetxService {
static final ApiService _instance = ApiService._internal();
ApiService._internal() {
_setupDio();
}
final Dio _dio = Dio();
String? get accessToken => Get.find<LocalStorageService>().getAccessToken();
factory ApiService() => _instance;
void _setupDio() {
_dio.options.baseUrl = Constants.BASE_URL;
_dio.options.connectTimeout = const Duration(seconds: 10);
_dio.options.receiveTimeout = const Duration(seconds: 10);
_dio.options.contentType = Headers.jsonContentType;
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) async {
final token = accessToken;
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
onResponse: (response, handler) => handler.next(response),
),
);
}
Future registerByPhone(String phone) async {
try {
await _dio.post(
'/register',
data: {'application': 'warehouse', 'phone': phone},
);
} catch (e) {
debugPrint('Регистрация по телефону: $e');
}
}
Future<bool> loginByPhone(String phone, String digCode) async {
try {
final response = await _dio.post(
'/login',
data: {
'phone': phone,
'digCode': digCode,
'application': 'warehouse',
}, // ← убран пробел
);
if (response.statusCode == 200) {
final data = response.data;
final token = data['token'];
final username = data['name'];
Get.find<LocalStorageService>().saveAccessToken(token);
Get.find<LocalStorageService>().putUsername(username);
return true;
}
} catch (e) {
debugPrint('Логин по телефону: $e');
}
return false;
}
Future<List<Task>> fetchTasks() async {
try {
final username = await Get.find<LocalStorageService>().getUsername();
final response = await _dio.get('/warehouse/tasks/$username');
if (response.statusCode == 200) {
final List<dynamic> list = response.data;
return list.map((item) => Task.fromMap(item)).toList();
} else {
debugPrint(
'Некорректный статус при загрузке задач: ${response.statusCode}',
);
}
} catch (e) {
debugPrint('Ошибка загрузки задач: $e');
}
return [];
}
Future<Task?> upsertTask(Task task) async {
if (task.isNew == true || task.id.isEmpty) {
final saved = await saveTask(task);
return saved;
} else {
final success = await updateTask(task);
return success ? task : null;
}
}
Future<Task?> saveTask(Task task) async {
try {
final response = await _dio.post('/warehouse/task', data: task.toMap());
if (response.statusCode == 200) {
return response.data;
}
} catch (e) {
debugPrint('Ошибка сохранения задачи: $e');
}
return null;
}
Future<bool> updateTask(Task task) async {
try {
debugPrint('Обновление задачи: ${task.toMap()}');
final response = await _dio.put('/warehouse/task', data: task.toMap());
return response.statusCode == 201;
} catch (e) {
debugPrint('Ошибка обновления задачи: $e');
}
return false;
}
Future<bool> deleteTask(String taskId) async {
try {
final response = await _dio.delete('/warehouse/task/$taskId');
return response.statusCode == 200;
} catch (e) {
debugPrint('Ошибка удаления задачи: $e');
}
return false;
}
void clearAuth() {
Get.find<LocalStorageService>().clearAuth();
}
static Future<ApiService> init() async {
final service = ApiService();
Get.put(service, permanent: true);
return service;
}
Future<bool> setFcmToken(String token) async {
try {
final response = await _dio.put(
'/warehouse/user',
data: {
"number": await Get.find<LocalStorageService>().getNumber(),
"notification": {
"token": token.toString(),
"enable": true,
"type": "FCM",
},
},
);
return response.statusCode == 200;
} catch (e) {
debugPrint('Ошибка обновление токена: $e');
}
return false;
}
}

View File

@@ -0,0 +1,151 @@
// lib/services/api/push_notification_service.dart
import 'dart:async';
import 'package:Stocky/Pages/controllers/MainController.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'api_service.dart'; // Убедитесь, что путь правильный
class PushNotificationService extends GetxService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
final StreamController<String?> _tokenController =
StreamController<String?>.broadcast();
final StreamController<RemoteMessage> _foregroundMessageController =
StreamController<RemoteMessage>.broadcast();
// Поток для уведомлений, пришедших при открытом приложении
Stream<RemoteMessage> get foregroundMessageStream =>
_foregroundMessageController.stream;
// Поток FCM-токена
Stream<String?> get tokenStream => _tokenController.stream;
@override
void onInit() {
_initFirebase();
super.onInit();
}
@override
void onClose() {
_tokenController.close();
_foregroundMessageController.close();
super.onClose();
}
Future<void> _initFirebase() async {
try {
await Firebase.initializeApp();
// Запрашиваем разрешение
await _requestPermission();
// Получаем токен
await _getToken();
// Настраиваем обработчики
_setupMessageHandling();
} catch (e) {
debugPrint('Ошибка инициализации FCM: $e');
}
}
Future<void> _requestPermission() async {
final status = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
// остальные по умолчанию false
);
if (status.authorizationStatus == AuthorizationStatus.authorized ||
status.authorizationStatus == AuthorizationStatus.provisional) {
debugPrint('Разрешение на уведомления получено');
} else {
debugPrint('Разрешение отклонено');
}
}
Future<void> _getToken() async {
try {
final String? token = await _messaging.getToken();
if (token != null) {
_tokenController.add(token);
print('FCM Token: $token');
// Отправляем токен на сервер — но только если ApiService уже инициализирован
if (Get.isRegistered<ApiService>()) {
Get.find<ApiService>().setFcmToken(token);
} else {
// Опционально: сохраните токен локально и отправьте позже
print('ApiService не готов — токен временно не отправлен');
}
}
} catch (e) {
debugPrint('Ошибка получения токена FCM: $e');
}
}
void _setupMessageHandling() {
// Уведомления, пришедшие при открытом приложении
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
debugPrint('Уведомление в foreground: ${message.notification?.title}');
Get.find<MainController>().fetchTasks();
_foregroundMessageController.add(message); // ← Передаём дальше
});
// Пользователь нажал на уведомление из фонового режима
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
debugPrint('Уведомление открыто из фона: ${message.notification?.title}');
_handleMessageAction(message);
});
// Фоновый обработчик (для закрытого приложения)
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
}
// Обработка действия из уведомления
void _handleMessageAction(RemoteMessage message) {
final data = message.data;
// Пример: действие зависит от поля "action" в payload
final action = data['action'];
switch (action) {
case 'open_task':
final taskId = data['task_id'];
if (taskId != null) {
// Здесь можно вызвать навигацию или обновить состояние
// Например, через Get.toNamed('/task/$taskId');
// Но лучше делегировать это MainController или другому слушателю
Get.find<MainController>().openTaskFromNotification(taskId);
}
break;
case 'refresh_tasks':
if (Get.isRegistered<MainController>()) {
Get.find<MainController>().fetchTasks();
}
break;
// Добавьте другие действия по мере необходимости
default:
debugPrint('Неизвестное действие: $action');
}
}
// Статический фоновый обработчик (ограниченный функционал)
static Future<void> _firebaseMessagingBackgroundHandler(
RemoteMessage message,
) async {
// В фоне нельзя использовать GetX, Dio с авторизацией и т.п.
// Только простые операции: логирование, запись в Hive и т.д.
debugPrint('Фоновое уведомление: ${message.messageId}');
// Пример: сохранить в локальную БД, чтобы обработать при запуске
}
}

View File

@@ -0,0 +1,88 @@
// lib/services/api/socket_service.dart
import 'dart:convert';
import 'package:flutter_stocky/utils/constants.dart';
import 'package:get/get.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class SocketService extends GetxService {
final String _baseUrl = Constants.BASE_SOCKET_URL;
WebSocketChannel? _channel;
bool _connected = false;
// Поток для получения сообщений
StreamController<Map<String, dynamic>> _messageController =
StreamController.broadcast();
Stream<Map<String, dynamic>> get messages => _messageController.stream;
@override
void onInit() {
connect();
super.onInit();
}
@override
void onClose() {
_disconnect();
_messageController.close();
super.onClose();
}
void connect() async {
if (_connected) return;
try {
_channel = WebSocketChannel.connect(Uri.parse('$_baseUrl/ws'));
_channel!.stream.listen(
(message) {
final data = json.decode(message);
_messageController.add(data);
},
onDone: () {
_connected = false;
debugPrint('WebSocket отключен');
},
onError: (error) {
debugPrint('WebSocket ошибка: $error');
_connected = false;
// Автоподключение через 3 сек
Future.delayed(const Duration(seconds: 3), connect);
},
);
_connected = true;
debugPrint('WebSocket подключен');
} catch (e) {
debugPrint('Ошибка подключения к WebSocket: $e');
}
}
void _disconnect() {
_channel?.close();
_channel = null;
_connected = false;
}
void sendMessage(Map<String, dynamic> message) {
if (_connected && _channel != null) {
_channel?.sink.add(json.encode(message));
}
}
// Пример: отправить обновление задачи
void notifyTaskUpdate(String taskId) {
sendMessage({'type': 'task_updated', 'task_id': taskId});
}
// Пример: подписаться на изменения задач
void subscribeToTasks() {
sendMessage({'type': 'subscribe', 'entity': 'tasks'});
}
// Пример: уведомить о создании задачи
void notifyTaskCreated(Task task) {
sendMessage({'type': 'task_created', 'data': task.toJson()});
}
bool get isConnected => _connected;
}

View File

@@ -0,0 +1,106 @@
// lib/services/local/local_storage_service.dart
import 'package:Stocky/models/task_adapter.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
class LocalStorageService extends GetxService {
static final LocalStorageService _instance = LocalStorageService._internal();
late Box _settBox;
late Box<Task> _tasksBox;
late Box _docsBox;
LocalStorageService._internal();
factory LocalStorageService() => _instance;
Box get settingsBox => _settBox;
Box<Task> get tasksBox => _tasksBox;
Box get docsBox => _docsBox;
List<Task> getTasks() {
return tasksBox.values.toList();
}
Future<List<Task>> getCompletedTasks() async {
return tasksBox.values.where((task) => !task.isCompleted).toList();
}
Future<void> putNumber(String number) async {
await settingsBox.put('number', number);
}
Future<String> getNumber() async {
return settingsBox.get('number');
}
Future<void> putUsername(String name) async {
await settingsBox.put('username', name);
}
Future<String> getUsername() async {
return settingsBox.get('username', defaultValue: 'john doe');
}
Future<void> saveTasks(List<Task> tasks) async {
await tasksBox.clear();
for (final task in tasks) {
await tasksBox.put(task.id.toString(), task);
}
}
Future<void> addTask(Task task) async {
await tasksBox.put(task.id.toString(), task);
}
Future<void> updateTask(Task task) async {
await tasksBox.put(task.id.toString(), task);
}
Future<void> replaceTask(String oldId, Task newTask) async {
await tasksBox.delete(oldId);
await tasksBox.put(newTask.id.toString(), newTask);
}
Future<void> removeTask(dynamic taskId) async {
await tasksBox.delete(taskId.toString());
}
// --- Токены ---
Future<void> saveAccessToken(String token) async {
await settingsBox.put('access_token', token);
}
Future<void> clearAuth() async {
await settingsBox.delete('access_token');
}
Future<void> saveFcmToken(String token) async {
await settingsBox.put('fcm_token', token);
}
String? getAccessToken() => settingsBox.get('access_token');
String? getFcmToken() => settingsBox.get('fcm_token');
static Future<LocalStorageService> init() async {
await Hive.initFlutter();
Hive.registerAdapter(TaskAdapter());
Hive.registerAdapter(SubtaskAdapter());
// Открываем боксы
await Hive.openBox('settings');
await Hive.openBox<Task>('tasks');
await Hive.openBox('docs');
// Теперь можно безопасно получить ссылки
final service = _instance;
service._settBox = Hive.box('settings');
service._tasksBox = Hive.box<Task>('tasks');
service._docsBox = Hive.box('docs');
Get.put(service, permanent: true);
return service;
}
}