generated from Grigo/AndroidTemplate
Major architecture update
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
package com.grigowashere.aismap.ui;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.grigowashere.aismap.models.AISVessel;
|
||||
import com.grigowashere.aismap.models.Vessel;
|
||||
import com.grigowashere.aismap.R;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class BottomSheetsBinder {
|
||||
private static final String TAG = "BottomSheetsBinder";
|
||||
private final Context context;
|
||||
|
||||
private BottomSheetDialog ownVesselBottomSheet;
|
||||
private BottomSheetDialog aisVesselBottomSheet;
|
||||
private View ownBottomSheetView;
|
||||
private View aisBottomSheetView;
|
||||
private AISVessel currentAISVessel;
|
||||
|
||||
// Auto update
|
||||
private android.os.Handler updateHandler;
|
||||
private Runnable updateRunnable;
|
||||
private int updateIntervalMs = 1000;
|
||||
|
||||
public BottomSheetsBinder(Context context) {
|
||||
this.context = context;
|
||||
this.updateHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
||||
}
|
||||
|
||||
public void setUpdateIntervalMs(int intervalMs) {
|
||||
this.updateIntervalMs = Math.max(250, intervalMs);
|
||||
}
|
||||
|
||||
public void init(View ownBottomSheetView) {
|
||||
this.ownBottomSheetView = ownBottomSheetView;
|
||||
ownVesselBottomSheet = new BottomSheetDialog(context);
|
||||
ownVesselBottomSheet.setContentView(ownBottomSheetView);
|
||||
ownVesselBottomSheet.setCanceledOnTouchOutside(true);
|
||||
ownVesselBottomSheet.setCancelable(true);
|
||||
}
|
||||
|
||||
public void initAIS(View aisView) {
|
||||
this.aisBottomSheetView = aisView;
|
||||
aisVesselBottomSheet = new BottomSheetDialog(context);
|
||||
aisVesselBottomSheet.setContentView(aisBottomSheetView);
|
||||
aisVesselBottomSheet.setCanceledOnTouchOutside(true);
|
||||
aisVesselBottomSheet.setCancelable(true);
|
||||
ImageButton btnCloseAIS = aisBottomSheetView.findViewById(R.id.btn_close_ais_bottom_sheet);
|
||||
if (btnCloseAIS != null) {
|
||||
btnCloseAIS.setOnClickListener(v -> aisVesselBottomSheet.dismiss());
|
||||
}
|
||||
}
|
||||
|
||||
public void showOwnVesselSheet() {
|
||||
if (ownVesselBottomSheet != null && !ownVesselBottomSheet.isShowing()) {
|
||||
// Обновление UI делегируется вызывающей стороне при необходимости
|
||||
ownVesselBottomSheet.show();
|
||||
startAutoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void showOwnVesselSheet(Runnable onUpdateStart) {
|
||||
if (ownVesselBottomSheet != null && !ownVesselBottomSheet.isShowing()) {
|
||||
if (onUpdateStart != null) onUpdateStart.run();
|
||||
ownVesselBottomSheet.show();
|
||||
startAutoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void showAISVesselSheet(AISVessel vessel) {
|
||||
this.currentAISVessel = vessel;
|
||||
if (aisVesselBottomSheet != null && !aisVesselBottomSheet.isShowing() && vessel != null) {
|
||||
// Обновление UI делегируется вызывающей стороне при необходимости
|
||||
aisVesselBottomSheet.show();
|
||||
startAutoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void showAISVesselSheet(AISVessel vessel, Runnable onUpdateStart) {
|
||||
this.currentAISVessel = vessel;
|
||||
if (aisVesselBottomSheet != null && !aisVesselBottomSheet.isShowing() && vessel != null) {
|
||||
if (onUpdateStart != null) onUpdateStart.run();
|
||||
aisVesselBottomSheet.show();
|
||||
startAutoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void startAutoUpdate() {
|
||||
if (updateRunnable != null) {
|
||||
updateHandler.removeCallbacks(updateRunnable);
|
||||
}
|
||||
updateRunnable = new Runnable() {
|
||||
@Override public void run() {
|
||||
try {
|
||||
// Обновление контента выполняется стороной-владельцем
|
||||
} finally {
|
||||
updateHandler.postDelayed(this, updateIntervalMs);
|
||||
}
|
||||
}
|
||||
};
|
||||
updateHandler.postDelayed(updateRunnable, updateIntervalMs);
|
||||
}
|
||||
|
||||
public void stopAutoUpdate() {
|
||||
if (updateRunnable != null) {
|
||||
updateHandler.removeCallbacks(updateRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateOwnVesselUI() { /* делегируется владельцу */ }
|
||||
|
||||
public void updateAISUI(AISVessel vessel) { /* делегируется владельцу */ }
|
||||
|
||||
public void updateAISTimeAgo() { /* делегируется владельцу */ }
|
||||
|
||||
private String safe(String s) { return s==null?"--":s; }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.grigowashere.aismap.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
|
||||
import com.grigowashere.aismap.R;
|
||||
import com.grigowashere.aismap.controllers.AppCoordinator;
|
||||
import com.grigowashere.aismap.models.AISVessel;
|
||||
import com.grigowashere.aismap.models.Vessel;
|
||||
|
||||
/**
|
||||
* Полный менеджер BottomSheet-ов: создание, показ, обновление и авто-обновление.
|
||||
* Делегирует всю прежнюю логику из MainActivity, чтобы разгрузить активити.
|
||||
*/
|
||||
public class BottomSheetsManager {
|
||||
|
||||
private static final String TAG = "BottomSheetsManager";
|
||||
|
||||
private final Context context;
|
||||
private final AppCoordinator appCoordinator;
|
||||
|
||||
private BottomSheetDialog ownVesselBottomSheet;
|
||||
private View bottomSheetView;
|
||||
|
||||
private BottomSheetDialog aisVesselBottomSheet;
|
||||
private View aisBottomSheetView;
|
||||
private AISVessel currentAISVessel;
|
||||
|
||||
private android.os.Handler timeUpdateHandler;
|
||||
private Runnable timeUpdateRunnable;
|
||||
|
||||
private android.os.Handler bottomSheetUpdateHandler;
|
||||
private Runnable bottomSheetUpdateRunnable;
|
||||
private static final int BOTTOM_SHEET_UPDATE_INTERVAL = 1000;
|
||||
|
||||
public BottomSheetsManager(Context context, AppCoordinator appCoordinator) {
|
||||
this.context = context;
|
||||
this.appCoordinator = appCoordinator;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
// Время
|
||||
timeUpdateHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
||||
timeUpdateRunnable = new Runnable() {
|
||||
@Override public void run() {
|
||||
if (currentAISVessel != null && aisVesselBottomSheet != null && aisVesselBottomSheet.isShowing()) {
|
||||
updateAISTimeAgo();
|
||||
}
|
||||
timeUpdateHandler.postDelayed(this, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// Авто-обновление контента
|
||||
bottomSheetUpdateHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
||||
bottomSheetUpdateRunnable = new Runnable() {
|
||||
@Override public void run() {
|
||||
if (ownVesselBottomSheet != null && ownVesselBottomSheet.isShowing()) {
|
||||
updateOwnVesselUI();
|
||||
}
|
||||
if (aisVesselBottomSheet != null && aisVesselBottomSheet.isShowing() && currentAISVessel != null) {
|
||||
updateAISBottomSheetUI(currentAISVessel);
|
||||
}
|
||||
bottomSheetUpdateHandler.postDelayed(this, BOTTOM_SHEET_UPDATE_INTERVAL);
|
||||
}
|
||||
};
|
||||
|
||||
// Наше судно
|
||||
ownVesselBottomSheet = new BottomSheetDialog(context);
|
||||
bottomSheetView = android.view.LayoutInflater.from(context).inflate(R.layout.bottom_sheet_own_vessel, null);
|
||||
ownVesselBottomSheet.setContentView(bottomSheetView);
|
||||
ImageButton btnClose = bottomSheetView.findViewById(R.id.btn_close_bottom_sheet);
|
||||
if (btnClose != null) {
|
||||
btnClose.setOnClickListener(v -> {
|
||||
ownVesselBottomSheet.dismiss();
|
||||
stopBottomSheetAutoUpdate();
|
||||
});
|
||||
}
|
||||
ownVesselBottomSheet.setCanceledOnTouchOutside(true);
|
||||
ownVesselBottomSheet.setCancelable(true);
|
||||
ownVesselBottomSheet.setOnDismissListener(dialog -> stopBottomSheetAutoUpdate());
|
||||
|
||||
// AIS судно
|
||||
aisVesselBottomSheet = new BottomSheetDialog(context);
|
||||
aisBottomSheetView = android.view.LayoutInflater.from(context).inflate(R.layout.bottom_sheet_ais_vessel, null);
|
||||
aisVesselBottomSheet.setContentView(aisBottomSheetView);
|
||||
ImageButton btnCloseAIS = aisBottomSheetView.findViewById(R.id.btn_close_ais_bottom_sheet);
|
||||
if (btnCloseAIS != null) {
|
||||
btnCloseAIS.setOnClickListener(v -> {
|
||||
aisVesselBottomSheet.dismiss();
|
||||
stopTimeUpdate();
|
||||
stopBottomSheetAutoUpdate();
|
||||
});
|
||||
}
|
||||
aisVesselBottomSheet.setCanceledOnTouchOutside(true);
|
||||
aisVesselBottomSheet.setCancelable(true);
|
||||
aisVesselBottomSheet.setOnDismissListener(dialog -> {
|
||||
stopTimeUpdate();
|
||||
stopBottomSheetAutoUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
public void showOwnVessel() {
|
||||
if (ownVesselBottomSheet != null && !ownVesselBottomSheet.isShowing()) {
|
||||
updateOwnVesselUI();
|
||||
ownVesselBottomSheet.show();
|
||||
startBottomSheetAutoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void showAISVessel(AISVessel vessel) {
|
||||
if (aisVesselBottomSheet != null && !aisVesselBottomSheet.isShowing()) {
|
||||
currentAISVessel = vessel;
|
||||
updateAISBottomSheetUI(vessel);
|
||||
aisVesselBottomSheet.show();
|
||||
startTimeUpdate();
|
||||
startBottomSheetAutoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateOwnVesselUI() {
|
||||
if (bottomSheetView == null) return;
|
||||
Vessel vessel = appCoordinator.getOwnVessel();
|
||||
if (vessel == null) return;
|
||||
|
||||
TextView tvStatus = bottomSheetView.findViewById(R.id.bottom_sheet_status);
|
||||
TextView tvPosition = bottomSheetView.findViewById(R.id.bottom_sheet_position);
|
||||
TextView tvCourse = bottomSheetView.findViewById(R.id.bottom_sheet_course);
|
||||
TextView tvSpeed = bottomSheetView.findViewById(R.id.bottom_sheet_speed);
|
||||
TextView tvAltitude = bottomSheetView.findViewById(R.id.bottom_sheet_altitude);
|
||||
TextView tvAccuracy = bottomSheetView.findViewById(R.id.bottom_sheet_accuracy);
|
||||
TextView tvGPSQuality = bottomSheetView.findViewById(R.id.bottom_sheet_gps_quality);
|
||||
TextView tvSatellites = bottomSheetView.findViewById(R.id.bottom_sheet_satellites);
|
||||
TextView tvDOP = bottomSheetView.findViewById(R.id.bottom_sheet_dop);
|
||||
TextView tvFixTime = bottomSheetView.findViewById(R.id.bottom_sheet_fix_time);
|
||||
TextView tvFixQuality = bottomSheetView.findViewById(R.id.bottom_sheet_fix_quality);
|
||||
|
||||
if (tvStatus != null) tvStatus.setText((vessel.getLatitude()!=0 && vessel.getLongitude()!=0) ? "Статус: GPS активен, данные получены" : "Статус: Ожидание GPS данных...");
|
||||
if (tvPosition != null) tvPosition.setText((vessel.getLatitude()!=0 && vessel.getLongitude()!=0) ? String.format("📍 Координаты: %.6f, %.6f", vessel.getLatitude(), vessel.getLongitude()) : "📍 Координаты: Не определены");
|
||||
if (tvCourse != null) tvCourse.setText((vessel.getCourse()>0) ? String.format("🧭 Курс: %.1f°", vessel.getCourse()) : "🧭 Курс: --°");
|
||||
if (tvSpeed != null) tvSpeed.setText((vessel.getSpeed()>0) ? String.format("⚡ Скорость: %.1f узлов", vessel.getSpeed()) : "⚡ Скорость: -- узлов");
|
||||
if (tvAltitude != null) tvAltitude.setText((vessel.getAltitude()!=0) ? String.format("🏔️ Высота: %.1f м", vessel.getAltitude()) : "🏔️ Высота: -- м");
|
||||
if (tvAccuracy != null) tvAccuracy.setText((vessel.getAccuracy()>0) ? String.format("🎯 Точность: %.1f м", vessel.getAccuracy()) : "🎯 Точность: -- м");
|
||||
if (tvGPSQuality != null) tvGPSQuality.setText((vessel.getGPSQualityDescription()!=null) ? String.format("📊 Качество GPS: %s", vessel.getGPSQualityDescription()) : "📊 Качество GPS: --");
|
||||
if (tvSatellites != null) tvSatellites.setText((vessel.getSatellites()>0) ? String.format("Спутники: %d/%d", vessel.getActiveSatellites(), vessel.getSatellites()) : "Спутники: --/--");
|
||||
if (tvDOP != null) tvDOP.setText((vessel.getPdop()>0) ? String.format("📈 DOP: PDOP=%.2f HDOP=%.2f VDOP=%.2f", vessel.getPdop(), vessel.getHdop(), vessel.getVdop()) : "📈 DOP: PDOP=-- HDOP=-- VDOP=--");
|
||||
if (tvFixTime != null) tvFixTime.setText((vessel.getFixTime()>0) ? String.format("🕐 Время фикса: %s", new java.text.SimpleDateFormat("HH:mm:ss", java.util.Locale.getDefault()).format(new java.util.Date(vessel.getFixTime()))) : "🕐 Время фикса: --");
|
||||
if (tvFixQuality != null) tvFixQuality.setText((vessel.getFixQuality()!=null) ? String.format("🔒 Качество фикса: %s", vessel.getFixQuality()) : "🔒 Качество фикса: --");
|
||||
}
|
||||
|
||||
public void updateAISBottomSheetUI(AISVessel vessel) {
|
||||
if (aisBottomSheetView == null || vessel == null) return;
|
||||
if (currentAISVessel != null && currentAISVessel.getMmsi() != null && currentAISVessel.getMmsi().equals(vessel.getMmsi())) {
|
||||
currentAISVessel = vessel;
|
||||
}
|
||||
|
||||
TextView tvTitle = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_title);
|
||||
TextView tvMmsi = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_mmsi);
|
||||
TextView tvCallsign = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_callsign);
|
||||
TextView tvImo = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_imo);
|
||||
TextView tvType = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_type);
|
||||
TextView tvPosition = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_position);
|
||||
TextView tvCourse = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_course);
|
||||
TextView tvRot = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_rot);
|
||||
TextView tvHeading = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_heading);
|
||||
TextView tvSpeed = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_speed);
|
||||
TextView tvDimensions = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_dimensions);
|
||||
TextView tvDraft = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_draft);
|
||||
TextView tvDestination = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_destination);
|
||||
TextView tvEta = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_eta);
|
||||
TextView tvNavStatus = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_nav_status);
|
||||
TextView tvClass = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_class);
|
||||
TextView tvSignal = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_signal);
|
||||
TextView tvDistance = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_distance);
|
||||
TextView tvBearing = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_bearing);
|
||||
TextView tvLastUpdate = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_last_update);
|
||||
TextView tvTimeAgo = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_time_ago);
|
||||
|
||||
if (tvTitle != null) {
|
||||
String name = vessel.getVesselName() != null && !vessel.getVesselName().isEmpty() ? vessel.getVesselName() : "AIS СУДНО";
|
||||
String flag = getFlagEmojiForMMSI(vessel.getMmsi());
|
||||
tvTitle.setText((flag != null ? flag + " " : "") + "🚢 " + name);
|
||||
}
|
||||
if (tvMmsi != null) tvMmsi.setText("🆔 MMSI: " + (vessel.getMmsi() != null ? vessel.getMmsi() : "--"));
|
||||
if (tvCallsign != null) tvCallsign.setText("📻 Позывной: " + (vessel.getCallSign() != null ? vessel.getCallSign() : "--"));
|
||||
if (tvImo != null) tvImo.setText("🏷️ IMO: " + (vessel.getImo() > 0 ? String.valueOf(vessel.getImo()) : "--"));
|
||||
if (tvType != null) tvType.setText("🚢 Тип: " + (vessel.getVesselType() != null ? vessel.getVesselType() : "--"));
|
||||
|
||||
if (tvPosition != null) {
|
||||
if (vessel.getLatitude() != 0 && vessel.getLongitude() != 0) {
|
||||
tvPosition.setText(String.format("📍 Координаты: %.6f, %.6f", vessel.getLatitude(), vessel.getLongitude()));
|
||||
} else {
|
||||
tvPosition.setText("📍 Координаты: --");
|
||||
}
|
||||
}
|
||||
if (tvCourse != null) tvCourse.setText(vessel.getCourse() > 0 ? String.format("🧭 COG: %.1f°", vessel.getCourse()) : "🧭 COG: --°");
|
||||
if (tvRot != null) tvRot.setText(vessel.getRateOfTurn() != 0 ? String.format("🔄 ROT: %.1f°/мин", vessel.getRateOfTurn()) : "🔄 ROT: --°/мин");
|
||||
if (tvHeading != null) tvHeading.setText(vessel.getHeading() > 0 ? String.format("🧭 HDG: %.1f°", vessel.getHeading()) : "🧭 HDG: --°");
|
||||
if (tvSpeed != null) tvSpeed.setText(vessel.getSpeed() > 0 ? String.format("⚡ Скорость: %.1f узлов", vessel.getSpeed()) : "⚡ Скорость: -- узлов");
|
||||
if (tvDimensions != null) tvDimensions.setText((vessel.getLength() > 0 && vessel.getWidth() > 0) ? String.format("📏 Размеры: %.1f x %.1f м", vessel.getLength(), vessel.getWidth()) : "📏 Размеры: --");
|
||||
if (tvDraft != null) tvDraft.setText(vessel.getDraft() > 0 ? String.format("🌊 Осадка: %.1f м", vessel.getDraft()) : "🌊 Осадка: -- м");
|
||||
if (tvDestination != null) tvDestination.setText("🎯 Назначение: " + (vessel.getDestination() != null ? vessel.getDestination() : "--"));
|
||||
if (tvEta != null) tvEta.setText(vessel.getEta() != null ? String.format("⏰ ETA: %s", vessel.getEta().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))) : "⏰ ETA: --");
|
||||
if (tvNavStatus != null) tvNavStatus.setText("🚦 Статус: " + (vessel.getNavigationalStatus() != null ? vessel.getNavigationalStatus() : "--"));
|
||||
if (tvClass != null) tvClass.setText("📋 Класс: " + (vessel.getVesselClass() != null ? vessel.getVesselClass() : "--"));
|
||||
if (tvSignal != null) {
|
||||
if (vessel.getSignalStrength() > 0) {
|
||||
tvSignal.setText(String.format("📶 Сигнал: %d", vessel.getSignalStrength()));
|
||||
} else {
|
||||
tvSignal.setText(vessel.isPositionAccuracy() ? "📶 Точность: высокая" : "📶 Точность: низкая");
|
||||
}
|
||||
}
|
||||
if (tvLastUpdate != null) tvLastUpdate.setText(vessel.getLastUpdate() != null ? String.format("🕐 Обновлено: %s", vessel.getLastUpdate().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss"))) : "🕐 Обновлено: --");
|
||||
|
||||
if (tvDistance != null || tvBearing != null) {
|
||||
Vessel ourVessel = appCoordinator.getOwnVessel();
|
||||
if (ourVessel != null && ourVessel.getLatitude() != 0 && ourVessel.getLongitude() != 0 && vessel.getLatitude() != 0 && vessel.getLongitude() != 0) {
|
||||
double distance = com.grigowashere.aismap.utils.NavigationUtils.calculateDistance(ourVessel.getLatitude(), ourVessel.getLongitude(), vessel.getLatitude(), vessel.getLongitude());
|
||||
if (tvDistance != null) tvDistance.setText("📏 Расстояние: " + com.grigowashere.aismap.utils.NavigationUtils.formatDistance(distance));
|
||||
double bearing = com.grigowashere.aismap.utils.NavigationUtils.calculateBearing(ourVessel.getLatitude(), ourVessel.getLongitude(), vessel.getLatitude(), vessel.getLongitude());
|
||||
double relativeBearing = com.grigowashere.aismap.utils.NavigationUtils.calculateRelativeBearing(ourVessel.getCourse(), bearing);
|
||||
if (tvBearing != null) tvBearing.setText("🧭 Пеленг: " + com.grigowashere.aismap.utils.NavigationUtils.formatRelativeBearing(relativeBearing));
|
||||
} else {
|
||||
if (tvDistance != null) tvDistance.setText("📏 Расстояние: --");
|
||||
if (tvBearing != null) tvBearing.setText("🧭 Пеленг: --");
|
||||
}
|
||||
}
|
||||
|
||||
if (tvTimeAgo != null) {
|
||||
if (vessel.getLastUpdate() != null) {
|
||||
long secondsAgo = java.time.Duration.between(vessel.getLastUpdate(), java.time.LocalDateTime.now()).getSeconds();
|
||||
tvTimeAgo.setText("⏱️ Время назад: " + formatTimeAgo(secondsAgo));
|
||||
} else {
|
||||
tvTimeAgo.setText("⏱️ Время назад: --");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startTimeUpdate() {
|
||||
if (timeUpdateHandler != null && timeUpdateRunnable != null) {
|
||||
timeUpdateHandler.postDelayed(timeUpdateRunnable, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopTimeUpdate() {
|
||||
if (timeUpdateHandler != null && timeUpdateRunnable != null) {
|
||||
timeUpdateHandler.removeCallbacks(timeUpdateRunnable);
|
||||
}
|
||||
currentAISVessel = null;
|
||||
}
|
||||
|
||||
public void startBottomSheetAutoUpdate() {
|
||||
if (bottomSheetUpdateHandler != null && bottomSheetUpdateRunnable != null) {
|
||||
bottomSheetUpdateHandler.removeCallbacks(bottomSheetUpdateRunnable);
|
||||
bottomSheetUpdateHandler.postDelayed(bottomSheetUpdateRunnable, BOTTOM_SHEET_UPDATE_INTERVAL);
|
||||
Log.i(TAG, "Автоматическое обновление BottomSheet запущено");
|
||||
}
|
||||
}
|
||||
|
||||
public void stopBottomSheetAutoUpdate() {
|
||||
if (bottomSheetUpdateHandler != null && bottomSheetUpdateRunnable != null) {
|
||||
bottomSheetUpdateHandler.removeCallbacks(bottomSheetUpdateRunnable);
|
||||
Log.i(TAG, "Автоматическое обновление BottomSheet остановлено");
|
||||
}
|
||||
}
|
||||
|
||||
public void updateAISTimeAgo() {
|
||||
if (aisBottomSheetView == null || currentAISVessel == null) return;
|
||||
TextView tvTimeAgo = aisBottomSheetView.findViewById(R.id.bottom_sheet_ais_time_ago);
|
||||
if (tvTimeAgo != null && currentAISVessel.getLastUpdate() != null) {
|
||||
long secondsAgo = java.time.Duration.between(currentAISVessel.getLastUpdate(), java.time.LocalDateTime.now()).getSeconds();
|
||||
tvTimeAgo.setText("⏱️ Время назад: " + formatTimeAgo(secondsAgo));
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
stopTimeUpdate();
|
||||
stopBottomSheetAutoUpdate();
|
||||
try { if (ownVesselBottomSheet != null) ownVesselBottomSheet.dismiss(); } catch (Exception ignore) {}
|
||||
try { if (aisVesselBottomSheet != null) aisVesselBottomSheet.dismiss(); } catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
private String formatTimeAgo(long seconds) {
|
||||
if (seconds < 60) return seconds + " сек";
|
||||
if (seconds < 3600) return (seconds / 60) + " мин";
|
||||
if (seconds < 86400) return (seconds / 3600) + " ч";
|
||||
return (seconds / 86400) + " дн";
|
||||
}
|
||||
|
||||
private String getFlagEmojiForMMSI(String mmsi) {
|
||||
try {
|
||||
if (mmsi == null || mmsi.length() < 3) return null;
|
||||
String mid = mmsi.substring(0, 3);
|
||||
String iso2 = com.grigowashere.aismap.utils.MIDToCountry.MID_TO_COUNTRY.get(mid);
|
||||
if (iso2 == null || iso2.length() != 2) return null;
|
||||
char a = Character.toUpperCase(iso2.charAt(0));
|
||||
char b = Character.toUpperCase(iso2.charAt(1));
|
||||
int base = 0x1F1E6;
|
||||
int cp1 = base + (a - 'A');
|
||||
int cp2 = base + (b - 'A');
|
||||
return new String(Character.toChars(cp1)) + new String(Character.toChars(cp2));
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.grigowashere.aismap.ui;
|
||||
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.util.Log;
|
||||
import com.grigowashere.aismap.R;
|
||||
import com.grigowashere.aismap.controllers.AppCoordinator;
|
||||
import com.grigowashere.aismap.utils.SettingsManager;
|
||||
|
||||
/**
|
||||
* Отвечает за работу с меню в MainActivity: создание и обработка пунктов
|
||||
*/
|
||||
public class MenuBinder {
|
||||
|
||||
private static final String TAG = "MenuBinder";
|
||||
private final AppCoordinator appCoordinator;
|
||||
private final SettingsManager settingsManager;
|
||||
private final MenuActions actions;
|
||||
|
||||
public MenuBinder(AppCoordinator appCoordinator, SettingsManager settingsManager, MenuActions actions) {
|
||||
this.appCoordinator = appCoordinator;
|
||||
this.settingsManager = settingsManager;
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Пока без переноса инфлейта; оставим точку расширения
|
||||
Log.d(TAG, "onCreateOptionsMenu: ready");
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
try {
|
||||
MenuItem gpsItem = menu.findItem(R.id.menu_gps);
|
||||
MenuItem udpItem = menu.findItem(R.id.menu_udp);
|
||||
if (gpsItem != null) {
|
||||
gpsItem.setTitle(appCoordinator.isAndroidNMEAEnabled() ? "GPS ✓" : "GPS");
|
||||
}
|
||||
if (udpItem != null) {
|
||||
udpItem.setTitle(appCoordinator.isUDPEnabled() ? "UDP ✓" : "UDP");
|
||||
}
|
||||
MenuItem pathItem = menu.findItem(R.id.menu_path_tracking);
|
||||
if (pathItem != null) {
|
||||
boolean pathEnabled = settingsManager.isPathTrackingEnabled();
|
||||
pathItem.setTitle(pathEnabled ? "Пути ✓" : "Пути");
|
||||
}
|
||||
MenuItem screenItem = menu.findItem(R.id.menu_keep_screen_on);
|
||||
if (screenItem != null) {
|
||||
boolean screenEnabled = settingsManager.isKeepScreenOnEnabled();
|
||||
screenItem.setTitle(screenEnabled ? "Экран ✓" : "Экран");
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "onPrepareOptionsMenu: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item == null) return false;
|
||||
int id = item.getItemId();
|
||||
try {
|
||||
if (id == R.id.menu_gps) {
|
||||
actions.toggleGPS();
|
||||
return true;
|
||||
} else if (id == R.id.menu_udp) {
|
||||
actions.toggleUDP();
|
||||
return true;
|
||||
} else if (id == R.id.menu_clear_ais) {
|
||||
actions.clearAIS();
|
||||
return true;
|
||||
} else if (id == R.id.menu_path_tracking) {
|
||||
actions.togglePathTracking();
|
||||
return true;
|
||||
} else if (id == R.id.menu_service_test) {
|
||||
actions.testForegroundService();
|
||||
return true;
|
||||
} else if (id == R.id.menu_keep_screen_on) {
|
||||
actions.toggleKeepScreenOn();
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "onOptionsItemSelected error: " + e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Действия меню, которые реализует MainActivity
|
||||
*/
|
||||
public interface MenuActions {
|
||||
void toggleGPS();
|
||||
void toggleUDP();
|
||||
void clearAIS();
|
||||
void togglePathTracking();
|
||||
void testForegroundService();
|
||||
void toggleKeepScreenOn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.grigowashere.aismap.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
/**
|
||||
* Инкапсулирует проверку и запрос runtime-разрешений
|
||||
*/
|
||||
public class PermissionsBinder {
|
||||
|
||||
private static final String TAG = "PermissionsBinder";
|
||||
private final Activity activity;
|
||||
|
||||
public PermissionsBinder(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public boolean ensurePermission(String permission, int requestCode) {
|
||||
if (ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
Log.d(TAG, "Requesting permission: " + permission);
|
||||
ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean handleOnRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults, int expectedCode, Runnable onGranted, Runnable onDenied) {
|
||||
if (requestCode != expectedCode) return false;
|
||||
boolean granted = grantResults != null && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||
if (granted) {
|
||||
if (onGranted != null) onGranted.run();
|
||||
} else {
|
||||
if (onDenied != null) onDenied.run();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.grigowashere.aismap.maps.MapInterface;
|
||||
import com.grigowashere.aismap.maps.MapInterfaceChangeListener;
|
||||
import com.grigowashere.aismap.models.Vessel;
|
||||
import com.grigowashere.aismap.models.AISVessel;
|
||||
|
||||
@@ -17,8 +18,9 @@ import java.util.HashMap;
|
||||
* Координатор UI отрисовки
|
||||
* Единая точка всех операций с картой и UI
|
||||
* Обеспечивает throttling и батчинг операций
|
||||
* Подписывается на изменения MapInterface для автоматического обновления
|
||||
*/
|
||||
public class UIRenderingCoordinator implements UIDataChangeNotifier {
|
||||
public class UIRenderingCoordinator implements UIDataChangeNotifier, MapInterfaceChangeListener {
|
||||
private static final String TAG = "UIRenderingCoordinator";
|
||||
|
||||
// Throttling интервалы
|
||||
@@ -178,8 +180,10 @@ public class UIRenderingCoordinator implements UIDataChangeNotifier {
|
||||
if (mapInterface == null) return;
|
||||
|
||||
try {
|
||||
// TODO: Реализовать батчинговое обновление путей
|
||||
Log.d(TAG, "Path updates выполнены (заглушка)");
|
||||
// Обновляем пути на карте
|
||||
// MapInterface должен обновить все пути AIS судов
|
||||
mapInterface.updateAllVesselPaths();
|
||||
Log.d(TAG, "Path updates выполнены");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка path updates: " + e.getMessage(), e);
|
||||
}
|
||||
@@ -275,4 +279,91 @@ public class UIRenderingCoordinator implements UIDataChangeNotifier {
|
||||
// Компас не относится к карте, передаем в MainActivity через callback
|
||||
Log.d(TAG, "Compass update: " + azimuth + "° - требует специальной обработки в MainActivity");
|
||||
}
|
||||
|
||||
/**
|
||||
* Реализация MapInterfaceChangeListener
|
||||
* Вызывается при смене MapInterface в MapController
|
||||
*/
|
||||
@Override
|
||||
public void onMapInterfaceChanged(MapInterface oldMapInterface, MapInterface newMapInterface) {
|
||||
Log.i(TAG, "🔄 MapInterface изменен в UIRenderingCoordinator");
|
||||
Log.i(TAG, " Старый: " + (oldMapInterface != null ? oldMapInterface.getClass().getSimpleName() : "null"));
|
||||
Log.i(TAG, " Новый: " + (newMapInterface != null ? newMapInterface.getClass().getSimpleName() : "null"));
|
||||
|
||||
// Обновляем ссылку на MapInterface
|
||||
this.mapInterface = newMapInterface;
|
||||
|
||||
if (newMapInterface != null) {
|
||||
Log.i(TAG, "✅ UIRenderingCoordinator обновлен с новым MapInterface");
|
||||
|
||||
// Переносим pending операции на новую карту
|
||||
transferPendingOperationsToNewMap();
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Новый MapInterface равен null - очищаем pending операции");
|
||||
clearPendingOperations();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Переносит pending операции на новую карту
|
||||
*/
|
||||
private void transferPendingOperationsToNewMap() {
|
||||
if (mapInterface == null) {
|
||||
Log.w(TAG, "⚠️ MapInterface равен null, нельзя перенести операции");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "🔄 Перенос pending операций на новую карту");
|
||||
|
||||
// Выполняем pending операции немедленно на новой карте
|
||||
uiHandler.post(() -> {
|
||||
try {
|
||||
// Выполняем pending vessel update
|
||||
if (pendingVesselUpdate != null) {
|
||||
Log.d(TAG, "📍 Выполняем pending vessel update");
|
||||
executeVesselUpdate();
|
||||
}
|
||||
|
||||
// Выполняем pending AIS updates
|
||||
if (!pendingAISUpdates.isEmpty()) {
|
||||
Log.d(TAG, "🚢 Выполняем " + pendingAISUpdates.size() + " pending AIS updates");
|
||||
executeAISUpdates();
|
||||
}
|
||||
|
||||
// Выполняем pending AIS removals
|
||||
if (!pendingAISRemovals.isEmpty()) {
|
||||
Log.d(TAG, "🗑️ Выполняем " + pendingAISRemovals.size() + " pending AIS removals");
|
||||
executeAISUpdates(); // Этот метод обрабатывает и удаления тоже
|
||||
}
|
||||
|
||||
Log.i(TAG, "✅ Pending операции перенесены на новую карту");
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "❌ Ошибка при переносе pending операций: " + e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает все pending операции
|
||||
*/
|
||||
private void clearPendingOperations() {
|
||||
Log.i(TAG, "🧹 Очистка всех pending операций");
|
||||
|
||||
pendingVesselUpdate = null;
|
||||
pendingAISUpdates.clear();
|
||||
pendingAISRemovals.clear();
|
||||
|
||||
// Сбрасываем флаги
|
||||
vesselUpdatePending = false;
|
||||
aisUpdatePending = false;
|
||||
pathUpdatePending = false;
|
||||
|
||||
// Отменяем все запланированные операции
|
||||
uiHandler.removeCallbacks(vesselUpdateRunnable);
|
||||
uiHandler.removeCallbacks(aisUpdateRunnable);
|
||||
uiHandler.removeCallbacks(pathUpdateRunnable);
|
||||
|
||||
Log.i(TAG, "✅ Pending операции очищены");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user