Skip to content
Snippets Groups Projects
Unverified Commit 9e335e0d authored by mueller-ma's avatar mueller-ma Committed by GitHub
Browse files

Support MapView in foss flavor (#1358)

parent 2f048289
No related branches found
No related tags found
No related merge requests found
...@@ -58,7 +58,7 @@ Before producing any amount of code please have a look at [contribution guidelin ...@@ -58,7 +58,7 @@ Before producing any amount of code please have a look at [contribution guidelin
## Build flavors ## Build flavors
An optional build flavor "foss" is available for distribution through F-Droid. This build has map view, FCM and crash reporting removed and will not be able to receive push notifications from openHAB Cloud. An optional build flavor "foss" is available for distribution through F-Droid. This build has FCM and crash reporting removed and will not be able to receive push notifications from openHAB Cloud.
For using map view support in the "full" build flavor, you need to visit the [Maps API page](https://developers.google.com/maps/android) and generate an API key via the 'Get a key' button at the top. Then add a line in the following format to the 'gradle.properties' file (either in the same directory as this readme file, or in $HOME/.gradle): `mapsApiKey=<key>`, replacing `<key>` with the API key you just obtained. For using map view support in the "full" build flavor, you need to visit the [Maps API page](https://developers.google.com/maps/android) and generate an API key via the 'Get a key' button at the top. Then add a line in the following format to the 'gradle.properties' file (either in the same directory as this readme file, or in $HOME/.gradle): `mapsApiKey=<key>`, replacing `<key>` with the API key you just obtained.
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<important_note>Important note</important_note> <important_note>Important note</important_note>
<oh_server>You need an openHAB server for this app</oh_server> <oh_server>You need an openHAB server for this app</oh_server>
<short_description>Vendor and technology agnostic open source home automation</short_description> <short_description>Vendor and technology agnostic open source home automation</short_description>
<fdroid>The builds on F-Droid have map view support, FCM and crash reporting removed and will not be able to receive push notifications from openHAB Cloud.</fdroid> <fdroid>The builds on F-Droid have FCM and crash reporting removed and will not be able to receive push notifications from openHAB Cloud.</fdroid>
<fdroid_beta><![CDATA[You can install the beta version alongside the <a href="https://f-droid.org/packages/org.openhab.habdroid/">stable version</a>.]]></fdroid_beta> <fdroid_beta><![CDATA[You can install the beta version alongside the <a href="https://f-droid.org/packages/org.openhab.habdroid/">stable version</a>.]]></fdroid_beta>
<fdroid_privacy_policy>Privacy policy: https://www.openhabfoundation.org/privacy.html</fdroid_privacy_policy> <fdroid_privacy_policy>Privacy policy: https://www.openhabfoundation.org/privacy.html</fdroid_privacy_policy>
<fdroid_anti_features>Anti Features</fdroid_anti_features> <fdroid_anti_features>Anti Features</fdroid_anti_features>
......
...@@ -112,8 +112,9 @@ dependencies { ...@@ -112,8 +112,9 @@ dependencies {
transitive false transitive false
exclude group: 'com.intellij', module: 'annotations' exclude group: 'com.intellij', module: 'annotations'
} }
// Google Maps // MapView support
fullImplementation 'com.google.android.gms:play-services-maps:16.1.0' fullImplementation 'com.google.android.gms:play-services-maps:16.1.0'
fossImplementation 'org.osmdroid:osmdroid-android:6.1.0'
// FCM // FCM
fullImplementation 'com.google.firebase:firebase-messaging:17.6.0' fullImplementation 'com.google.firebase:firebase-messaging:17.6.0'
......
...@@ -15,10 +15,8 @@ import org.hamcrest.Description; ...@@ -15,10 +15,8 @@ import org.hamcrest.Description;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.openhab.habdroid.BuildConfig;
import org.openhab.habdroid.R; import org.openhab.habdroid.R;
import org.openhab.habdroid.TestWithoutIntro; import org.openhab.habdroid.TestWithoutIntro;
import org.openhab.habdroid.util.Util;
import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.Espresso.onView;
...@@ -100,12 +98,10 @@ public class BasicWidgetTest extends TestWithoutIntro { ...@@ -100,12 +98,10 @@ public class BasicWidgetTest extends TestWithoutIntro {
.perform(scrollToPosition(10)) .perform(scrollToPosition(10))
.check(matches(atPositionOnView(10, isDisplayed(), R.id.stop_button))); .check(matches(atPositionOnView(10, isDisplayed(), R.id.stop_button)));
if (Util.isFlavorFull()) {
// check whether map view is displayed // check whether map view is displayed
recyclerView recyclerView
.perform(scrollToPosition(13)) .perform(scrollToPosition(13))
.check(matches(atPositionOnView(13, isDisplayed(), "MapView"))); .check(matches(atPositionOnView(13, isDisplayed(), R.id.mapview)));
}
} }
public interface ChildViewCallback { public interface ChildViewCallback {
......
package org.openhab.habdroid.ui; package org.openhab.habdroid.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.Resources;
import android.location.Location;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import org.openhab.habdroid.R;
import org.openhab.habdroid.core.connection.Connection; import org.openhab.habdroid.core.connection.Connection;
import org.openhab.habdroid.model.Item;
import org.openhab.habdroid.model.ParsedState;
import org.openhab.habdroid.model.Widget;
import org.openhab.habdroid.util.Util;
import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.events.MapEventsReceiver;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
import org.osmdroid.util.BoundingBox;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.CopyrightOverlay;
import org.osmdroid.views.overlay.MapEventsOverlay;
import org.osmdroid.views.overlay.Marker;
import java.util.ArrayList;
import java.util.Locale;
public class MapViewHelper { public class MapViewHelper {
private static final String TAG = MapViewHelper.class.getSimpleName();
public static WidgetAdapter.ViewHolder createViewHolder(LayoutInflater inflater, public static WidgetAdapter.ViewHolder createViewHolder(LayoutInflater inflater,
ViewGroup parent, Connection connection, WidgetAdapter.ColorMapper colorMapper) { ViewGroup parent, Connection connection, WidgetAdapter.ColorMapper colorMapper) {
return new WidgetAdapter.GenericViewHolder(inflater, parent, connection, colorMapper); Context context = inflater.getContext();
Configuration.getInstance().load(context,
PreferenceManager.getDefaultSharedPreferences(context));
return new OsmViewHolder(inflater, parent, connection, colorMapper);
}
private static class OsmViewHolder extends WidgetAdapter.LabeledItemBaseViewHolder
implements Marker.OnMarkerDragListener {
private final MapView mMapView;
private final Handler mHandler;
private final int mRowHeightPixels;
private Item mBoundItem;
private boolean mStarted;
public OsmViewHolder(LayoutInflater inflater, ViewGroup parent,
Connection connection, WidgetAdapter.ColorMapper colorMapper) {
super(inflater, parent, R.layout.openhabwidgetlist_mapitem, connection, colorMapper);
mHandler = new Handler();
mMapView = itemView.findViewById(R.id.mapview);
mMapView.setTileSource(TileSourceFactory.MAPNIK);
mMapView.setVerticalMapRepetitionEnabled(false);
mMapView.getOverlays().add(new CopyrightOverlay(itemView.getContext()));
mMapView.setBuiltInZoomControls(false);
mMapView.setMultiTouchControls(false);
mMapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() {
@Override
public boolean singleTapConfirmedHelper(GeoPoint p) {
openPopup();
return true;
}
@Override
public boolean longPressHelper(GeoPoint p) {
return false;
}
}));
applyPositionAndLabelWhenReady(mMapView, 15.0f, false, false);
final Resources res = itemView.getContext().getResources();
mRowHeightPixels = res.getDimensionPixelSize(R.dimen.row_height);
}
@Override
public void bind(Widget widget) {
super.bind(widget);
ViewGroup.LayoutParams lp = mMapView.getLayoutParams();
int rows = widget.height() > 0 ? widget.height() : 5;
int desiredHeightPixels = rows * mRowHeightPixels;
if (lp.height != desiredHeightPixels) {
lp.height = desiredHeightPixels;
mMapView.setLayoutParams(lp);
}
mBoundItem = widget.item();
applyPositionAndLabelWhenReady(mMapView, 15.0f, false, false);
}
@Override
public void start() {
super.start();
if (!mStarted) {
mMapView.onResume();
mStarted = true;
}
}
@Override
public void stop() {
super.stop();
if (mStarted) {
mMapView.onPause();
mStarted = false;
}
}
@Override
public void onMarkerDragStart(Marker marker) {
// no-op, we're interested in drag end only
}
@Override
public void onMarkerDrag(Marker marker) {
// no-op, we're interested in drag end only
}
@Override
public void onMarkerDragEnd(Marker marker) {
String newState = String.format(Locale.US, "%f,%f",
marker.getPosition().getLatitude(), marker.getPosition().getLongitude());
String item = marker.getId();
Util.sendItemCommand(mConnection.getAsyncHttpClient(), item, newState);
}
private void openPopup() {
final MapView mapView = new MapView(itemView.getContext());
AlertDialog dialog = new AlertDialog.Builder(itemView.getContext())
.setView(mapView)
.setCancelable(true)
.setNegativeButton(R.string.close, null)
.create();
dialog.setOnDismissListener(dialogInterface -> mapView.onPause());
dialog.setCanceledOnTouchOutside(true);
dialog.show();
mapView.setBuiltInZoomControls(true);
mapView.setMultiTouchControls(true);
mapView.setVerticalMapRepetitionEnabled(false);
mapView.getOverlays().add(new CopyrightOverlay(itemView.getContext()));
mapView.onResume();
applyPositionAndLabelWhenReady(mapView, 16.0f, true, true);
}
private void applyPositionAndLabelWhenReady(MapView mapView, float zoomLevel,
boolean allowDrag, boolean allowScroll) {
mHandler.post(() -> applyPositionAndLabel(mapView, zoomLevel, allowDrag, allowScroll));
}
private void applyPositionAndLabel(MapView mapView, float zoomLevel, boolean allowDrag,
boolean allowScroll) {
if (mBoundItem == null) {
return;
}
boolean canDragMarker = allowDrag && !mBoundItem.readOnly();
if (!mBoundItem.members().isEmpty()) {
ArrayList<GeoPoint> positions = new ArrayList<>();
for (Item item : mBoundItem.members()) {
GeoPoint position = toGeoPoint(item.state());
if (position != null) {
setMarker(mapView, position, item, item.label(), canDragMarker,
this);
positions.add(position);
}
}
if (!positions.isEmpty()) {
double north = -90;
double south = 90;
double west = 180;
double east = -180;
for (GeoPoint position : positions) {
north = Math.max(position.getLatitude(), north);
south = Math.min(position.getLatitude(), south);
west = Math.min(position.getLongitude(), west);
east = Math.max(position.getLongitude(), east);
}
Log.d(TAG, String.format("North %f, south %f, west %f, east %f",
north, south, west, east));
BoundingBox boundingBox = new BoundingBox(north, east, south, west);
int extraPixel = (int) convertDpToPixel(24f, mapView.getContext());
try {
mapView.zoomToBoundingBox(boundingBox, false, extraPixel);
} catch (Exception e) {
Log.d(TAG, "Error applying markers", e);
}
if (!allowScroll) {
mapView.setScrollableAreaLimitLongitude(west, east, extraPixel);
mapView.setScrollableAreaLimitLatitude(north, south, extraPixel);
}
}
} else {
GeoPoint position = toGeoPoint(mBoundItem.state());
if (position != null) {
setMarker(mapView, position, mBoundItem, mLabelView.getText(), canDragMarker,
this);
moveCamera(mapView, zoomLevel, position);
if (!allowScroll) {
mapView.setScrollableAreaLimitLatitude(position.getLatitude(),
position.getLatitude(), 0);
mapView.setScrollableAreaLimitLongitude(position.getLongitude(),
position.getLongitude(), 0);
}
}
}
}
/**
* This method converts dp unit to equivalent pixels, depending on device density.
*
* @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
* @param context Context to get resources and device specific display metrics
* @return A float value to represent px equivalent to dp depending on device density
*/
private static float convertDpToPixel(float dp, Context context){
return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
private static void moveCamera(MapView mapView, float zoom, GeoPoint geoPoint) {
IMapController mapController = mapView.getController();
mapController.setZoom(zoom);
mapController.setCenter(geoPoint);
}
private static void setMarker(MapView mapView, GeoPoint position, Item item,
CharSequence label, boolean canDrag,
Marker.OnMarkerDragListener onMarkerDragListener) {
Marker marker = new Marker(mapView);
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
marker.setDraggable(canDrag);
marker.setPosition(position);
marker.setTitle(label != null ? label.toString() : null);
marker.setId(item.link());
marker.setOnMarkerDragListener(onMarkerDragListener);
marker.setIcon(ContextCompat.getDrawable(mapView.getContext(),
R.drawable.ic_location_on_red_24dp));
mapView.getOverlays().add(marker);
}
private static GeoPoint toGeoPoint(ParsedState state) {
Location location = state != null ? state.asLocation() : null;
return location != null ? new GeoPoint(location) : null;
}
} }
} }
\ No newline at end of file
<vector android:height="36dp" android:tint="#EA4335"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="36dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/widgetlist_item_left_margin"
android:paddingRight="@dimen/widgetlist_item_right_margin"
android:background="?android:activatedBackgroundIndicator"
android:paddingStart="@dimen/widgetlist_item_left_margin"
android:paddingEnd="@dimen/widgetlist_item_right_margin"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<org.openhab.habdroid.ui.widget.WidgetImageView
android:id="@+id/widgeticon"
android:layout_width="32dp"
android:layout_height="32dp"
android:padding="4dp" />
<TextView
android:id="@+id/widgetlabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<org.osmdroid.views.MapView
android:id="@+id/mapview"
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_margin="4dp" />
</LinearLayout>
...@@ -34,13 +34,11 @@ ...@@ -34,13 +34,11 @@
</LinearLayout> </LinearLayout>
<!-- tag used by unit test, don't remove -->
<com.google.android.gms.maps.MapView <com.google.android.gms.maps.MapView
android:id="@+id/mapview" android:id="@+id/mapview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="180dp" android:layout_height="180dp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:tag="MapView"
app:liteMode="true" /> app:liteMode="true" />
</LinearLayout> </LinearLayout>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment