diff --git a/codec2talkie/build.gradle b/codec2talkie/build.gradle index 8809cf9..9215ba4 100644 --- a/codec2talkie/build.gradle +++ b/codec2talkie/build.gradle @@ -39,6 +39,7 @@ dependencies { implementation 'com.github.mik3y:usb-serial-for-android:3.4.3' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.lifecycle:lifecycle-livedata:2.5.1' testImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/maps/MapActivity.java b/codec2talkie/src/main/java/com/radio/codec2talkie/maps/MapActivity.java index 7a14a20..9f773ec 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/maps/MapActivity.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/maps/MapActivity.java @@ -44,11 +44,13 @@ import org.osmdroid.views.overlay.Polygon; import org.osmdroid.views.overlay.Polyline; import org.osmdroid.views.overlay.compass.CompassOverlay; import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider; +import org.osmdroid.views.overlay.infowindow.MarkerInfoWindow; import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -57,22 +59,24 @@ public class MapActivity extends AppCompatActivity { private MapView _map; private IMapController _mapController; - private CompassOverlay _compassOverlay; private MyLocationNewOverlay _myLocationNewOverlay; - private final HashMap _objectOverlayItems = new HashMap<>(); - private final HashMap _objectOverlayRangeCircles = new HashMap<>(); - - private StationItemViewModel _stationItemViewModel; private PositionItemViewModel _positionItemViewModel; private AprsSymbolTable _aprsSymbolTable; + private MarkerInfoWindow _infoWindow; - private String _mySymbolCode; + // live settings private boolean _rotateMap = false; private boolean _showCircles = false; - private LiveData> _stationTrack; - List _stationTrackPoints = new ArrayList<>(); - Polyline _stationTrackLine = new Polyline(); //see note below! + // stations and circles + private final HashMap _objectOverlayItems = new HashMap<>(); + private final HashMap _objectOverlayRangeCircles = new HashMap<>(); + + // track + private LiveData> _activeTrackLiveData; + private final HashSet _activeTrackTimestamps = new HashSet<>(); + private final List _activeTrackPoints = new ArrayList<>(); + private final Polyline _activeTrackLine = new Polyline(); @Override protected void onCreate(Bundle savedInstanceState) { @@ -88,12 +92,13 @@ public class MapActivity extends AppCompatActivity { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); _aprsSymbolTable = AprsSymbolTable.getInstance(context); - _mySymbolCode = sharedPreferences.getString(PreferenceKeys.APRS_SYMBOL, "/["); + String mySymbolCode = sharedPreferences.getString(PreferenceKeys.APRS_SYMBOL, "/["); // map _map = findViewById(R.id.map); _map.setTileSource(TileSourceFactory.MAPNIK); _map.setMultiTouchControls(true); + _infoWindow = new MarkerInfoWindow(R.layout.bonuspack_bubble, _map); // controller _mapController = _map.getController(); @@ -109,13 +114,13 @@ public class MapActivity extends AppCompatActivity { super.onSensorChanged(sensorEvent); } }; - _compassOverlay = new CompassOverlay(context, compassOrientationProvider, _map); - _compassOverlay.enableCompass(); - _map.getOverlays().add(_compassOverlay); + CompassOverlay compassOverlay = new CompassOverlay(context, compassOrientationProvider, _map); + compassOverlay.enableCompass(); + _map.getOverlays().add(compassOverlay); // my location _myLocationNewOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(context), _map); - Bitmap myBitmapIcon = _aprsSymbolTable.bitmapFromSymbol(_mySymbolCode, true); + Bitmap myBitmapIcon = _aprsSymbolTable.bitmapFromSymbol(mySymbolCode, true); _myLocationNewOverlay.setDirectionIcon(myBitmapIcon); _myLocationNewOverlay.setPersonIcon(myBitmapIcon); @@ -131,27 +136,27 @@ public class MapActivity extends AppCompatActivity { _positionItemViewModel = new ViewModelProvider(this).get(PositionItemViewModel.class); // station items, add data listener - _stationItemViewModel = new ViewModelProvider(this).get(StationItemViewModel.class); + StationItemViewModel _stationItemViewModel = new ViewModelProvider(this).get(StationItemViewModel.class); + // FIXME, room livedata sends all list if one item changed event with distinctUntilChanged _stationItemViewModel.getAllStationItems().observe(this, allStations -> { + Log.i(TAG, "add stations " + allStations.size()); for (StationItem station : allStations) { //Log.i(TAG, "new position " + station.getLatitude() + " " + station.getLongitude()); // do not add items without coordinate if (station.getMaidenHead() == null) continue; if (addStationPositionIcon(station)) { addRangeCircle(station); - } else { - Log.e(TAG, "Failed to add APRS icon for " + station.getSrcCallsign() + ", " + station.getSymbolCode()); } } }); // add track - Paint p = _stationTrackLine.getOutlinePaint(); + Paint p = _activeTrackLine.getOutlinePaint(); p.setStrokeWidth(8); p.setColor(Color.RED); p.setStyle(Paint.Style.STROKE); p.setPathEffect(new DashPathEffect(new float[] {10f, 10f}, 0f)); - _map.getOverlayManager().add(_stationTrackLine); + _map.getOverlayManager().add(_activeTrackLine); } @Override @@ -194,21 +199,41 @@ public class MapActivity extends AppCompatActivity { } private void addTrack(List positions) { + boolean shouldSet = false; for (PositionItem trackPoint : positions) { - //Log.i(TAG, "addPoint " + trackPoint.getLatitude() + " " + trackPoint.getLongitude()); - _stationTrackPoints.add(new GeoPoint(trackPoint.getLatitude(), trackPoint.getLongitude())); + if (!_activeTrackTimestamps.contains(trackPoint.getTimestampEpoch())) { + Log.i(TAG, "addPoint " + trackPoint.getTimestampEpoch() + " " + trackPoint.getLatitude() + " " + trackPoint.getLongitude()); + GeoPoint point = new GeoPoint(trackPoint.getLatitude(), trackPoint.getLongitude()); + _activeTrackPoints.add(point); + _activeTrackTimestamps.add(trackPoint.getTimestampEpoch()); + shouldSet = true; + } } - _stationTrackLine.setPoints(_stationTrackPoints); + if (shouldSet) + _activeTrackLine.setPoints(_activeTrackPoints); } private boolean addStationPositionIcon(StationItem group) { String callsign = group.getSrcCallsign(); Marker marker = null; + String newTitle = DateTools.epochToIso8601(group.getTimestampEpoch()) + " " + callsign; + String newSnippet = getStatus(group); + // find old marker if (_objectOverlayItems.containsKey(callsign)) { marker = _objectOverlayItems.get(callsign); assert marker != null; + + // skip if unchanged + GeoPoint oldPosition = marker.getPosition(); + if (oldPosition.getLatitude() == group.getLatitude() && + oldPosition.getLongitude() == group.getLongitude() && + marker.getTitle().equals(newTitle) && + marker.getSnippet().equals(newSnippet)) { + + return false; + } } // create new marker @@ -274,25 +299,28 @@ public class MapActivity extends AppCompatActivity { marker.setId(callsign); marker.setIcon(drawableText); marker.setImage(drawableInfoIcon); - /* marker.setOnMarkerClickListener((monitoredMarker, mapView) -> { - if (_stationTrack != null) - _stationTrack.removeObservers(this); - _map.getOverlays().remove(_stationTrackLine); - _stationTrackPoints.clear(); - _stationTrackLine.setPoints(_stationTrackPoints); - _map.getOverlays().add(_stationTrackLine); - _stationTrack = _positionItemViewModel.getPositionItems(monitoredMarker.getId()); - _stationTrack.observe(this, this::addTrack); + GeoPoint markerPoint = monitoredMarker.getPosition(); + _infoWindow.open(monitoredMarker, new GeoPoint(markerPoint.getLatitude(), markerPoint.getLongitude()), 0, -2*height); + if (_activeTrackLiveData != null) + _activeTrackLiveData.removeObservers(this); + _map.getOverlays().remove(_activeTrackLine); + _activeTrackPoints.clear(); + _activeTrackTimestamps.clear(); + _activeTrackLine.setPoints(_activeTrackPoints); + _map.getOverlays().add(_activeTrackLine); + // FIXME, room livedata sends all list if one item changed event with distinctUntilChanged + _activeTrackLiveData = _positionItemViewModel.getPositionItems(monitoredMarker.getId()); + _activeTrackLiveData.observe(this, this::addTrack); return false; }); - */ _map.getOverlays().add(marker); _objectOverlayItems.put(callsign, marker); } + marker.setPosition(new GeoPoint(group.getLatitude(), group.getLongitude())); - marker.setTitle(DateTools.epochToIso8601(group.getTimestampEpoch()) + " " + callsign); - marker.setSnippet(getStatus(group)); + marker.setTitle(newTitle); + marker.setSnippet(newSnippet); return true; } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItem.java b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItem.java index ad2fc21..0ccfa05 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItem.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItem.java @@ -108,10 +108,12 @@ public class PositionItem { public void setRangeMiles(double rangeMiles) { this.rangeMiles = rangeMiles; } - public static boolean equalTo(PositionItem positionItem1, PositionItem positionItem2) { - return positionItem1.getSrcCallsign().equals(positionItem2.getSrcCallsign()) & - positionItem1.getIsTransmit() == positionItem2.getIsTransmit() && - Math.abs(positionItem1.getLongitude() - positionItem2.getLongitude()) <= MIN_COORDINATE_CHANGE_DELTA & - Math.abs(positionItem1.getLatitude() - positionItem2.getLatitude()) <= MIN_COORDINATE_CHANGE_DELTA; + @Override + public boolean equals(Object o) { + PositionItem positionItem = (PositionItem) o; + return getSrcCallsign().equals(positionItem.getSrcCallsign()) & + getIsTransmit() == positionItem.getIsTransmit() && + Math.abs(getLongitude() - positionItem.getLongitude()) <= MIN_COORDINATE_CHANGE_DELTA & + Math.abs(getLatitude() - positionItem.getLatitude()) <= MIN_COORDINATE_CHANGE_DELTA; } } \ No newline at end of file diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItemDao.java b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItemDao.java index ad237a9..98db0bd 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItemDao.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItemDao.java @@ -24,7 +24,7 @@ public abstract class PositionItemDao { @Transaction public void upsertPositionItem(PositionItem positionItem) { PositionItem oldPosition = getLastPositionItem(positionItem.getSrcCallsign()); - if (oldPosition != null && PositionItem.equalTo(positionItem, oldPosition)) { + if (oldPosition != null && oldPosition.equals(positionItem)) { // update id and coordinates from existing position positionItem.setId(oldPosition.getId()); positionItem.setLatitude(oldPosition.getLatitude()); diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItemRepository.java b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItemRepository.java index cbb1f76..679146d 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItemRepository.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/position/PositionItemRepository.java @@ -3,6 +3,8 @@ package com.radio.codec2talkie.storage.position; import android.app.Application; import android.util.Log; import androidx.lifecycle.LiveData; +import androidx.lifecycle.Transformations; + import com.radio.codec2talkie.storage.AppDatabase; @@ -23,7 +25,7 @@ public class PositionItemRepository { } public LiveData> getPositionItems(String srcCallsign) { - return _positionItemDao.getPositionItems(srcCallsign); + return Transformations.distinctUntilChanged(_positionItemDao.getPositionItems(srcCallsign)); } public void deleteAllPositionItems() { diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/station/StationItem.java b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/station/StationItem.java index 0e86187..cae72c1 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/station/StationItem.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/station/StationItem.java @@ -1,10 +1,14 @@ package com.radio.codec2talkie.storage.station; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.room.Entity; import androidx.room.Index; import androidx.room.PrimaryKey; +import java.util.Objects; + @Entity(indices = {@Index(value = {"srcCallsign"}, unique = true)}) public class StationItem { @NonNull @@ -118,4 +122,15 @@ public class StationItem { if (stationItem.getLogLine() != null) setLogLine(stationItem.getLogLine()); } + + @Override + public boolean equals(Object o) { + StationItem stationItem = (StationItem)o; + return srcCallsign.equals(stationItem.getSrcCallsign()) && + timestampEpoch == stationItem.getTimestampEpoch() && + Objects.equals(comment, stationItem.getComment()) && + Objects.equals(dstCallsign, stationItem.getDstCallsign()) && + latitude == stationItem.getLatitude() && + longitude == stationItem.getLongitude(); + } } diff --git a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/station/StationItemRepository.java b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/station/StationItemRepository.java index 1f8a024..8e8646a 100644 --- a/codec2talkie/src/main/java/com/radio/codec2talkie/storage/station/StationItemRepository.java +++ b/codec2talkie/src/main/java/com/radio/codec2talkie/storage/station/StationItemRepository.java @@ -5,6 +5,7 @@ import android.database.sqlite.SQLiteConstraintException; import android.util.Log; import androidx.lifecycle.LiveData; +import androidx.lifecycle.Transformations; import com.radio.codec2talkie.storage.AppDatabase; @@ -19,7 +20,7 @@ public class StationItemRepository { public StationItemRepository(Application application) { AppDatabase appDatabase = AppDatabase.getDatabase(application); _stationItemDao = appDatabase.stationitemDao(); - _stationItems = _stationItemDao.getAllStationItems(); + _stationItems = Transformations.distinctUntilChanged(_stationItemDao.getAllStationItems()); } public LiveData> getAllStationItems() { return _stationItems; }