generated from Grigo/AndroidTemplate
Подготовка к крупным изменениям: карта, AIS и UI
- Яндекс/MapForge: правки в менеджерах и обёртках маркеров (улучшена отрисовка/логика) - NMEAParser: корректировки парсинга и стабильности - Модель AISVessel: уточнение полей/логики - Настройки: правки в SettingsActivity и SettingsManager, актуализация AppController - UI: обновлены activity_main, activity_settings, bottom_sheet_ais_vessel; меню main_menu - Ресурсы: добавлен drawable/targetclassa.xml, обновлён drawable/target.xml - Конфигурация: правки AndroidManifest и app/build.gradle - Прочее: изменения в .idea (не влияют на сборку)
This commit is contained in:
@@ -5,6 +5,9 @@ import android.util.Log;
|
||||
import com.grigowashere.aismap.models.Vessel;
|
||||
import com.grigowashere.aismap.models.AISVessel;
|
||||
import com.grigowashere.aismap.maps.MapInterface;
|
||||
import com.grigowashere.aismap.data.Repository;
|
||||
import com.grigowashere.aismap.data.mapper.AISVesselMapper;
|
||||
import com.grigowashere.aismap.services.NotificationService;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -34,6 +37,8 @@ public class AppController implements
|
||||
private Vessel ownVessel;
|
||||
private List<AISVessel> aisVessels;
|
||||
private ExecutorService executor;
|
||||
private com.grigowashere.aismap.data.Repository repository;
|
||||
private NotificationService notificationService;
|
||||
|
||||
private boolean isUDPEnabled;
|
||||
private boolean isAndroidNMEAEnabled;
|
||||
@@ -42,6 +47,15 @@ public class AppController implements
|
||||
private int udpPort;
|
||||
private String dataMode;
|
||||
|
||||
// Время последнего получения сообщений ($ GPS) и (! AIS) в elapsedRealtime
|
||||
private long lastGPSMessageRealtimeMs;
|
||||
private long lastAISMessageRealtimeMs;
|
||||
|
||||
// Периодическая очистка БД от устаревших AIS целей
|
||||
private android.os.Handler dbCleanupHandler;
|
||||
private Runnable dbCleanupRunnable;
|
||||
private static final long DB_CLEANUP_INTERVAL = 60000; // 1 минута
|
||||
|
||||
// Callback для обновления UI
|
||||
private UIUpdateCallback uiUpdateCallback;
|
||||
|
||||
@@ -64,6 +78,12 @@ public class AppController implements
|
||||
this.ownVessel = new Vessel();
|
||||
this.aisVessels = new ArrayList<>();
|
||||
this.executor = Executors.newCachedThreadPool();
|
||||
this.repository = new com.grigowashere.aismap.data.Repository(context);
|
||||
this.notificationService = new NotificationService(context);
|
||||
|
||||
// Инициализируем Handler для периодической очистки БД
|
||||
this.dbCleanupHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
||||
this.dbCleanupRunnable = this::performDatabaseCleanup;
|
||||
|
||||
initializeControllers();
|
||||
}
|
||||
@@ -92,6 +112,29 @@ public class AppController implements
|
||||
// Инициализация Android NMEA слушателя (для курса, скорости, DOP)
|
||||
androidNmeaListener = new AndroidNMEAListener(context);
|
||||
androidNmeaListener.setCallback(this);
|
||||
|
||||
// Восстанавливаем данные из БД при старте
|
||||
try {
|
||||
com.grigowashere.aismap.data.entity.VesselEntity latest = repository.getLatestOwnVesselSync();
|
||||
if (latest != null) {
|
||||
ownVessel.setLatitude(latest.latitude);
|
||||
ownVessel.setLongitude(latest.longitude);
|
||||
ownVessel.setAccuracy(latest.accuracy);
|
||||
ownVessel.setFixTime(latest.fixTime);
|
||||
}
|
||||
java.util.List<com.grigowashere.aismap.data.entity.AISVesselEntity> list = repository.getAllAISSync();
|
||||
if (list != null) {
|
||||
for (com.grigowashere.aismap.data.entity.AISVesselEntity entity : list) {
|
||||
// Используем маппер для полного восстановления всех полей
|
||||
AISVessel vessel = AISVesselMapper.toModel(entity);
|
||||
aisVessels.add(vessel);
|
||||
Log.d(TAG, "AIS судно восстановлено из БД с полными данными: " + vessel.getMmsi());
|
||||
}
|
||||
Log.i(TAG, "Восстановлено " + list.size() + " AIS судов из БД с полными данными");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка восстановления данных из БД: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,6 +147,24 @@ public class AppController implements
|
||||
Log.i(TAG, "Устанавливаем MarkerClickListener в MapInterface");
|
||||
mapInterface.setMarkerClickListener(this);
|
||||
Log.i(TAG, "MarkerClickListener установлен, теперь можно создавать маркеры");
|
||||
|
||||
// Восстановление отрисовки сохранённых данных на карте
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
|
||||
try {
|
||||
// Позиция нашего судна
|
||||
if (ownVessel != null && ownVessel.getLatitude() != 0 && ownVessel.getLongitude() != 0) {
|
||||
mapInterface.updateOwnVesselPosition(ownVessel);
|
||||
}
|
||||
// AIS маркеры
|
||||
if (aisVessels != null && !aisVessels.isEmpty()) {
|
||||
for (AISVessel v : aisVessels) {
|
||||
mapInterface.addAISVesselMarker(v);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка восстановления отрисовки на карте: " + e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +196,8 @@ public class AppController implements
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Запускаем периодическую очистку БД от устаревших AIS целей
|
||||
startDatabaseCleanup();
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +206,9 @@ public class AppController implements
|
||||
* Останавливает все слушатели
|
||||
*/
|
||||
public void stopAllListeners() {
|
||||
// Останавливаем периодическую очистку БД
|
||||
stopDatabaseCleanup();
|
||||
|
||||
executor.execute(() -> {
|
||||
udpListener.stop();
|
||||
androidNmeaListener.stopListening();
|
||||
@@ -279,15 +344,28 @@ public class AppController implements
|
||||
ownVessel.setFixTime(vessel.getFixTime());
|
||||
ownVessel.setFixQuality(vessel.getFixQuality());
|
||||
|
||||
// Сохраняем позицию в локальную БД
|
||||
try {
|
||||
com.grigowashere.aismap.data.entity.VesselEntity ve = new com.grigowashere.aismap.data.entity.VesselEntity();
|
||||
ve.latitude = ownVessel.getLatitude();
|
||||
ve.longitude = ownVessel.getLongitude();
|
||||
ve.accuracy = ownVessel.getAccuracy();
|
||||
ve.fixTime = ownVessel.getFixTime();
|
||||
repository.upsertOwnVessel(ve);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка сохранения позиции в БД: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// Обновляем UI через callback
|
||||
if (uiUpdateCallback != null) {
|
||||
uiUpdateCallback.onVesselPositionUpdated(ownVessel);
|
||||
}
|
||||
|
||||
// Обновляем карту в главном потоке
|
||||
// Обновляем карту в главном потоке с throttling
|
||||
if (mapInterface != null) {
|
||||
Log.i(TAG, "Обновляем позицию на карте...");
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
|
||||
// Используем postDelayed для предотвращения частых обновлений
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
|
||||
try {
|
||||
Log.i(TAG, "Вызываем mapInterface.updateOwnVesselPosition...");
|
||||
mapInterface.updateOwnVesselPosition(ownVessel);
|
||||
@@ -295,7 +373,7 @@ public class AppController implements
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка обновления позиции на карте: " + e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}, 100); // Задержка 100мс для throttling
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +459,17 @@ public class AppController implements
|
||||
AISVessel existingVessel = findAISVesselByMMSI(vessel.getMmsi());
|
||||
|
||||
if (existingVessel != null) {
|
||||
// Если пришло новое safety-сообщение (тип 14), уведомим пользователя
|
||||
if (vessel.getLastSafetyMessage() != null && !vessel.getLastSafetyMessage().isEmpty()) {
|
||||
String prev = existingVessel.getLastSafetyMessage();
|
||||
String curr = vessel.getLastSafetyMessage();
|
||||
if (prev == null || !prev.equals(curr)) {
|
||||
if (notificationService != null && notificationService.areNotificationsEnabled()) {
|
||||
notificationService.notifySafetyMessage(vessel.getMmsi(), curr);
|
||||
}
|
||||
}
|
||||
existingVessel.setLastSafetyMessage(curr);
|
||||
}
|
||||
// Обновляем существующее судно
|
||||
existingVessel.updatePosition(
|
||||
vessel.getLatitude(),
|
||||
@@ -388,6 +477,14 @@ public class AppController implements
|
||||
vessel.getCourse(),
|
||||
vessel.getSpeed()
|
||||
);
|
||||
try {
|
||||
// Используем маппер для полной конвертации всех полей
|
||||
com.grigowashere.aismap.data.entity.AISVesselEntity entity = AISVesselMapper.toEntity(existingVessel);
|
||||
repository.upsertAIS(entity);
|
||||
Log.d(TAG, "AIS судно сохранено в БД с полными данными: " + existingVessel.getMmsi());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка апсерта AIS в БД: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (mapInterface != null) {
|
||||
// Используем Handler для выполнения в главном потоке
|
||||
@@ -402,6 +499,28 @@ public class AppController implements
|
||||
} else {
|
||||
// Добавляем новое судно
|
||||
aisVessels.add(vessel);
|
||||
|
||||
// Если это новое судно сразу пришло с safety-сообщением — уведомим
|
||||
if (vessel.getLastSafetyMessage() != null && !vessel.getLastSafetyMessage().isEmpty()) {
|
||||
if (notificationService != null && notificationService.areNotificationsEnabled()) {
|
||||
notificationService.notifySafetyMessage(vessel.getMmsi(), vessel.getLastSafetyMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Воспроизводим уведомление о новой цели
|
||||
if (notificationService != null && notificationService.areNotificationsEnabled()) {
|
||||
notificationService.notifyNewAISTarget();
|
||||
Log.i(TAG, "🔔 Уведомление о новой AIS цели: " + vessel.getMmsi());
|
||||
}
|
||||
|
||||
try {
|
||||
// Используем маппер для полной конвертации всех полей
|
||||
com.grigowashere.aismap.data.entity.AISVesselEntity entity = AISVesselMapper.toEntity(vessel);
|
||||
repository.upsertAIS(entity);
|
||||
Log.d(TAG, "Новое AIS судно сохранено в БД с полными данными: " + vessel.getMmsi());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка апсерта AIS в БД: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (mapInterface != null) {
|
||||
// Используем Handler для выполнения в главном потоке
|
||||
@@ -465,6 +584,8 @@ public class AppController implements
|
||||
|
||||
// Парсим полученные данные как NMEA
|
||||
nmeaParser.parseNMEA(data);
|
||||
// Обновляем метки времени по префиксу
|
||||
updateLastMessageAgesFromRaw(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -485,6 +606,18 @@ public class AppController implements
|
||||
|
||||
// Парсим полученные данные как NMEA
|
||||
nmeaParser.parseNMEA(message);
|
||||
if (message != null) {
|
||||
String trimmed = message.trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
char c = trimmed.charAt(0);
|
||||
long now = android.os.SystemClock.elapsedRealtime();
|
||||
if (c == '$') {
|
||||
lastGPSMessageRealtimeMs = now;
|
||||
} else if (c == '!') {
|
||||
lastAISMessageRealtimeMs = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация MarkerClickListener
|
||||
@@ -577,11 +710,56 @@ public class AppController implements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запускает периодическую очистку БД от устаревших AIS целей
|
||||
*/
|
||||
public void startDatabaseCleanup() {
|
||||
if (dbCleanupHandler != null && dbCleanupRunnable != null) {
|
||||
dbCleanupHandler.postDelayed(dbCleanupRunnable, DB_CLEANUP_INTERVAL);
|
||||
Log.i(TAG, "Запущена периодическая очистка БД от устаревших AIS целей");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Останавливает периодическую очистку БД
|
||||
*/
|
||||
public void stopDatabaseCleanup() {
|
||||
if (dbCleanupHandler != null && dbCleanupRunnable != null) {
|
||||
dbCleanupHandler.removeCallbacks(dbCleanupRunnable);
|
||||
Log.i(TAG, "Остановлена периодическая очистка БД от устаревших AIS целей");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет очистку БД от устаревших AIS целей
|
||||
*/
|
||||
private void performDatabaseCleanup() {
|
||||
try {
|
||||
com.grigowashere.aismap.utils.SettingsManager settingsManager =
|
||||
new com.grigowashere.aismap.utils.SettingsManager(context);
|
||||
|
||||
int staleRemoveMinutes = settingsManager.getDataStaleRemoveMinutes();
|
||||
long thresholdEpochMs = System.currentTimeMillis() - (staleRemoveMinutes * 60 * 1000L);
|
||||
|
||||
repository.deleteStaleAIS(thresholdEpochMs);
|
||||
|
||||
Log.i(TAG, "Выполнена очистка БД от AIS целей старше " + staleRemoveMinutes + " минут");
|
||||
|
||||
// Планируем следующую очистку
|
||||
if (dbCleanupHandler != null && dbCleanupRunnable != null) {
|
||||
dbCleanupHandler.postDelayed(dbCleanupRunnable, DB_CLEANUP_INTERVAL);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка при очистке БД от устаревших AIS целей: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Освобождает ресурсы
|
||||
*/
|
||||
public void cleanup() {
|
||||
stopAllListeners();
|
||||
stopDatabaseCleanup();
|
||||
|
||||
if (udpListener != null) {
|
||||
udpListener.cleanup();
|
||||
@@ -595,11 +773,51 @@ public class AppController implements
|
||||
gpsLocationListener.cleanup();
|
||||
}
|
||||
|
||||
if (notificationService != null) {
|
||||
notificationService.cleanup();
|
||||
}
|
||||
|
||||
if (executor != null && !executor.isShutdown()) {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Метки времени последних сообщений ($ и !) =====
|
||||
private void updateLastMessageAgesFromRaw(String raw) {
|
||||
if (raw == null) return;
|
||||
long now = android.os.SystemClock.elapsedRealtime();
|
||||
String[] lines = raw.split("\r?\n");
|
||||
for (String line : lines) {
|
||||
if (line == null) continue;
|
||||
String t = line.trim();
|
||||
if (t.isEmpty()) continue;
|
||||
char c = t.charAt(0);
|
||||
if (c == '$') {
|
||||
lastGPSMessageRealtimeMs = now;
|
||||
break;
|
||||
} else if (c == '!') {
|
||||
lastAISMessageRealtimeMs = now;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Возвращает секунды с последнего GPS ($) сообщения; -1 если не было */
|
||||
public int getSecondsSinceLastGPSMessage() {
|
||||
if (lastGPSMessageRealtimeMs <= 0) return -1;
|
||||
long diff = android.os.SystemClock.elapsedRealtime() - lastGPSMessageRealtimeMs;
|
||||
if (diff < 0) return 0;
|
||||
return (int)(diff / 1000L);
|
||||
}
|
||||
|
||||
/** Возвращает секунды с последнего AIS (!) сообщения; -1 если не было */
|
||||
public int getSecondsSinceLastAISMessage() {
|
||||
if (lastAISMessageRealtimeMs <= 0) return -1;
|
||||
long diff = android.os.SystemClock.elapsedRealtime() - lastAISMessageRealtimeMs;
|
||||
if (diff < 0) return 0;
|
||||
return (int)(diff / 1000L);
|
||||
}
|
||||
|
||||
// Методы для управления настройками
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user