Merge branch 'mapping' of https://github.com/SamWhited/Conversations into SamWhited-mapping
|  | @ -0,0 +1,110 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:xlink="http://www.w3.org/1999/xlink" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="48" | ||||
|    height="48" | ||||
|    viewBox="0 0 48 48" | ||||
|    id="svg2" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.91 r13725" | ||||
|    sodipodi:docname="marker.svg"> | ||||
|   <metadata | ||||
|      id="metadata10"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title /> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <defs | ||||
|      id="defs8"> | ||||
|     <radialGradient | ||||
|        inkscape:collect="always" | ||||
|        xlink:href="#linearGradient3913" | ||||
|        id="radialGradient3883" | ||||
|        gradientUnits="userSpaceOnUse" | ||||
|        gradientTransform="matrix(0.2039074,-0.09024614,0.07170697,0.16216229,-92.579229,-90.973095)" | ||||
|        cx="262.33273" | ||||
|        cy="945.23846" | ||||
|        fx="262.33273" | ||||
|        fy="945.23846" | ||||
|        r="185.49754" /> | ||||
|     <linearGradient | ||||
|        inkscape:collect="always" | ||||
|        id="linearGradient3913"> | ||||
|       <stop | ||||
|          style="stop-color:#ffffff;stop-opacity:1;" | ||||
|          offset="0" | ||||
|          id="stop3915" /> | ||||
|       <stop | ||||
|          style="stop-color:#ffffff;stop-opacity:0;" | ||||
|          offset="1" | ||||
|          id="stop3917" /> | ||||
|     </linearGradient> | ||||
|     <clipPath | ||||
|        clipPathUnits="userSpaceOnUse" | ||||
|        id="clipPath4167"> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          d="M 24,4.0000001 C 16.27,4.0000001 10,10.27 10,18 10,28.5 24,44 24,44 24,44 38,28.5 38,18 38,10.27 31.73,4.0000001 24,4.0000001 Z M 24,23 c -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z" | ||||
|          id="path4169" | ||||
|          style="fill:#000000;fill-opacity:1" /> | ||||
|     </clipPath> | ||||
|     <clipPath | ||||
|        clipPathUnits="userSpaceOnUse" | ||||
|        id="clipPath4321"> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          d="m 24,4.0001492 c -7.73,0 -14,6.2699998 -14,14.0000008 0,10.5 14,26 14,26 0,0 14,-15.5 14,-26 C 38,10.270149 31.73,4.0001492 24,4.0001492 Z M 24,23.00015 c -2.76,0 -5,-2.24 -5,-5 0,-2.760001 2.24,-5.000001 5,-5.000001 2.76,0 5,2.24 5,5.000001 0,2.76 -2.24,5 -5,5 z" | ||||
|          id="path4323" | ||||
|          style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:18, 3;stroke-dashoffset:0;stroke-opacity:0.53333285" /> | ||||
|     </clipPath> | ||||
|   </defs> | ||||
|   <sodipodi:namedview | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1" | ||||
|      objecttolerance="10" | ||||
|      gridtolerance="10" | ||||
|      guidetolerance="10" | ||||
|      inkscape:pageopacity="0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="1010" | ||||
|      id="namedview6" | ||||
|      showgrid="false" | ||||
|      inkscape:zoom="4.9166667" | ||||
|      inkscape:cx="-15.254237" | ||||
|      inkscape:cy="12.20339" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="41" | ||||
|      inkscape:window-maximized="1" | ||||
|      inkscape:current-layer="svg2" /> | ||||
|   <path | ||||
|      d="M24 4c-7.73 0-14 6.27-14 14 0 10.5 14 26 14 26s14-15.5 14-26c0-7.73-6.27-14-14-14zm0 19c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z" | ||||
|      id="path4" | ||||
|      style="fill:#00a000;fill-opacity:1;stroke:none;stroke-opacity:0.53333336;stroke-width:1.70000002;stroke-miterlimit:4;stroke-dasharray:none" /> | ||||
|   <path | ||||
|      style="display:inline;opacity:0.19211821;fill:url(#radialGradient3883);fill-opacity:1;stroke:none" | ||||
|      d="m 53.884912,1.7373006 c -18.322492,0 -33.173092,14.5823714 -33.173092,32.5686504 0,3.794038 0.661899,7.436601 1.877335,10.821463 1.505391,0.209531 3.044508,0.317391 4.607513,0.317391 5.584539,0 9.890238,-1.147853 14.805425,-2.934259 l 15.611481,6.295152 a 2.0568126,2.0577227 0 0 0 2.766588,-2.403594 l -4.227888,-17.09591 c 2.717518,-4.771967 3.645449,-10.205846 3.645449,-15.810885 0,-4.0761111 -0.781533,-7.9714274 -2.20495,-11.5551094 -1.217366,-0.132888 -2.454715,-0.202899 -3.707861,-0.202899 z" | ||||
|      id="path3878" | ||||
|      inkscape:connector-curvature="0" | ||||
|      clip-path="url(#clipPath4167)" /> | ||||
|   <path | ||||
|      inkscape:connector-curvature="0" | ||||
|      d="M 24,4.0000003 C 16.27,4.0000003 10,10.27 10,18 10,28.5 24,44 24,44 24,44 38,28.5 38,18 38,10.27 31.73,4.0000003 24,4.0000003 Z M 24,23 c -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z" | ||||
|      id="path4-3" | ||||
|      style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:30,5;stroke-opacity:0.53333336;stroke-dashoffset:44" | ||||
|      clip-path="url(#clipPath4321)" /> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 4.7 KiB | 
|  | @ -68,6 +68,7 @@ images = { | |||
| 	'message_bubble_sent_grey.svg' => ['message_bubble_sent_grey.9', 0], | ||||
| 	'date_bubble_white.svg' => ['date_bubble_white.9', 0], | ||||
| 	'date_bubble_grey.svg' => ['date_bubble_grey.9', 0], | ||||
| 	'marker.svg' => ['marker', 0] | ||||
| 	} | ||||
| 
 | ||||
| # Executable paths for Mac OSX | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ dependencies { | |||
|     implementation "com.wefika:flowlayout:0.4.1" | ||||
|     implementation 'net.ypresto.androidtranscoder:android-transcoder:0.2.0' | ||||
|     implementation 'rocks.xmpp:xmpp-addr:0.8.0-SNAPSHOT' | ||||
|     implementation 'org.osmdroid:osmdroid-android:6.0.1' | ||||
| } | ||||
| 
 | ||||
| ext { | ||||
|  |  | |||
|  | @ -13,6 +13,13 @@ | |||
|     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | ||||
|     <uses-permission android:name="android.permission.VIBRATE" /> | ||||
|     <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> | ||||
|     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> | ||||
|     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> | ||||
|     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> | ||||
| 
 | ||||
|     <uses-feature android:name="android.hardware.location" android:required="false" /> | ||||
|     <uses-feature android:name="android.hardware.location.gps" android:required="false" /> | ||||
|     <uses-feature android:name="android.hardware.location.network" android:required="false" /> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.CAMERA" /> | ||||
| 
 | ||||
|  | @ -49,7 +56,27 @@ | |||
|                 <action android:name="android.media.RINGER_MODE_CHANGED" /> | ||||
|             </intent-filter> | ||||
|         </receiver> | ||||
| 
 | ||||
|         <activity | ||||
|             android:name=".ui.ShareLocationActivity" | ||||
|             android:label="@string/title_activity_share_location" > | ||||
|             <intent-filter> | ||||
|                 <action android:name="eu.siacs.conversations.location.request" /> | ||||
|                 <category android:name="android.intent.category.DEFAULT" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name=".ui.ShowLocationActivity" | ||||
|             android:label="@string/title_activity_show_location" > | ||||
|             <intent-filter> | ||||
|                 <action android:name="eu.siacs.conversations.location.show" /> | ||||
|                 <category android:name="android.intent.category.DEFAULT" /> | ||||
|             </intent-filter> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.VIEW" /> | ||||
|                 <data android:scheme="geo" /> | ||||
|                 <category android:name="android.intent.category.DEFAULT" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name=".ui.ConversationActivity" | ||||
|             android:theme="@style/SplashTheme"> | ||||
|  |  | |||
|  | @ -2,7 +2,8 @@ package eu.siacs.conversations; | |||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import org.osmdroid.util.GeoPoint; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
|  | @ -10,8 +11,6 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState; | |||
| import rocks.xmpp.addr.Jid; | ||||
| 
 | ||||
| public final class Config { | ||||
| 
 | ||||
| 
 | ||||
| 	private static final int UNENCRYPTED = 1; | ||||
| 	private static final int OPENPGP = 2; | ||||
| 	private static final int OTR = 4; | ||||
|  | @ -160,4 +159,15 @@ public final class Config { | |||
| 
 | ||||
| 	private Config() { | ||||
| 	} | ||||
| 
 | ||||
| 	public static final class Map { | ||||
| 		public final static double INITIAL_ZOOM_LEVEL = 4; | ||||
| 		public final static double FINAL_ZOOM_LEVEL = 15; | ||||
| 		public final static GeoPoint INITIAL_POS = new GeoPoint(33.805278, -84.171389); | ||||
| 		public final static int MY_LOCATION_INDICATOR_SIZE = 10; | ||||
| 		public final static int MY_LOCATION_INDICATOR_OUTLINE_SIZE = 3; | ||||
| 		public final static long LOCATION_FIX_TIME_DELTA = 1000 * 10; // ms | ||||
| 		public final static float LOCATION_FIX_SPACE_DELTA = 10; // m | ||||
| 		public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| package eu.siacs.conversations.ui; | ||||
| 
 | ||||
| import android.support.v7.app.ActionBar; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| 
 | ||||
| public abstract class ActionBarActivity extends AppCompatActivity { | ||||
|     public static void configureActionBar(ActionBar actionBar) { | ||||
|         if (actionBar != null) { | ||||
|             actionBar.setHomeButtonEnabled(true); | ||||
|             actionBar.setDisplayHomeAsUpEnabled(true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,314 @@ | |||
| package eu.siacs.conversations.ui; | ||||
| 
 | ||||
| import android.Manifest; | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.BitmapFactory; | ||||
| import android.location.Location; | ||||
| import android.location.LocationListener; | ||||
| import android.location.LocationManager; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.provider.Settings; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Log; | ||||
| import android.view.MenuItem; | ||||
| 
 | ||||
| import org.osmdroid.api.IGeoPoint; | ||||
| import org.osmdroid.api.IMapController; | ||||
| import org.osmdroid.config.Configuration; | ||||
| import org.osmdroid.config.IConfigurationProvider; | ||||
| import org.osmdroid.tileprovider.tilesource.XYTileSource; | ||||
| import org.osmdroid.util.GeoPoint; | ||||
| import org.osmdroid.views.MapView; | ||||
| import org.osmdroid.views.overlay.Overlay; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| import eu.siacs.conversations.BuildConfig; | ||||
| import eu.siacs.conversations.Config; | ||||
| import eu.siacs.conversations.R; | ||||
| import eu.siacs.conversations.ui.util.LocationHelper; | ||||
| import eu.siacs.conversations.ui.widget.Marker; | ||||
| import eu.siacs.conversations.ui.widget.MyLocation; | ||||
| import eu.siacs.conversations.utils.ThemeHelper; | ||||
| 
 | ||||
| public abstract class LocationActivity extends ActionBarActivity implements LocationListener { | ||||
| 	protected LocationManager locationManager; | ||||
| 	protected boolean hasLocationFeature; | ||||
| 
 | ||||
| 	public static final int REQUEST_CODE_CREATE = 0; | ||||
| 	public static final int REQUEST_CODE_FAB_PRESSED = 1; | ||||
| 	public static final int REQUEST_CODE_SNACKBAR_PRESSED = 2; | ||||
| 
 | ||||
| 	protected static final String KEY_LOCATION = "loc"; | ||||
| 	protected static final String KEY_ZOOM_LEVEL = "zoom"; | ||||
| 
 | ||||
| 	protected Location myLoc = null; | ||||
| 	protected MapView map = null; | ||||
| 	protected IMapController mapController = null; | ||||
| 
 | ||||
| 	protected Bitmap marker_icon; | ||||
| 
 | ||||
| 	protected void clearMarkers() { | ||||
| 		synchronized (this.map.getOverlays()) { | ||||
| 			for (final Overlay overlay : this.map.getOverlays()) { | ||||
| 				if (overlay instanceof Marker || overlay instanceof MyLocation) { | ||||
| 					this.map.getOverlays().remove(overlay); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	protected void updateLocationMarkers() { | ||||
| 		clearMarkers(); | ||||
| 	} | ||||
| 
 | ||||
| 	protected XYTileSource tileSource() { | ||||
| 		return new XYTileSource("OpenStreetMap", | ||||
| 				0, 19, 256, ".png", new String[] { | ||||
| 				"https://a.tile.openstreetmap.org/", | ||||
| 				"https://b.tile.openstreetmap.org/", | ||||
| 				"https://c.tile.openstreetmap.org/" },"© OpenStreetMap contributors"); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onCreate(final Bundle savedInstanceState) { | ||||
| 		super.onCreate(savedInstanceState); | ||||
| 		final Context ctx = getApplicationContext(); | ||||
| 		setTheme(ThemeHelper.find(this)); | ||||
| 
 | ||||
| 		final PackageManager packageManager = ctx.getPackageManager(); | ||||
| 		hasLocationFeature = packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION) || | ||||
| 				packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS) || | ||||
| 				packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_NETWORK); | ||||
| 		this.locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); | ||||
| 		this.marker_icon = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.marker); | ||||
| 
 | ||||
| 		// Ask for location permissions if location services are enabled and we're | ||||
| 		// just starting the activity (we don't want to keep pestering them on every | ||||
| 		// screen rotation or if there's no point because it's disabled anyways). | ||||
| 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && savedInstanceState == null) { | ||||
| 			requestPermissions(REQUEST_CODE_CREATE); | ||||
| 		} | ||||
| 
 | ||||
| 		final IConfigurationProvider config = Configuration.getInstance(); | ||||
| 		config.load(ctx, getPreferences()); | ||||
| 		config.setUserAgentValue(BuildConfig.APPLICATION_ID + "_" + BuildConfig.VERSION_CODE); | ||||
| 
 | ||||
| 		final File f = new File(ctx.getCacheDir() + "/tiles"); | ||||
| 		try { | ||||
| 			//noinspection ResultOfMethodCallIgnored | ||||
| 			f.mkdirs(); | ||||
| 		} catch (final SecurityException ignored) { | ||||
| 		} | ||||
| 		if (f.exists() && f.isDirectory() && f.canRead() && f.canWrite()) { | ||||
| 			Log.d(Config.LOGTAG, "Using tile cache at: " + f.getAbsolutePath()); | ||||
| 			config.setOsmdroidTileCache(f.getAbsoluteFile()); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onSaveInstanceState(@NonNull final Bundle outState) { | ||||
| 		super.onSaveInstanceState(outState); | ||||
| 
 | ||||
| 		final IGeoPoint center = map.getMapCenter(); | ||||
| 		outState.putParcelable(KEY_LOCATION, new GeoPoint( | ||||
| 				center.getLatitude(), | ||||
| 				center.getLongitude() | ||||
| 		)); | ||||
| 		outState.putDouble(KEY_ZOOM_LEVEL, map.getZoomLevelDouble()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { | ||||
| 		super.onRestoreInstanceState(savedInstanceState); | ||||
| 
 | ||||
| 		if (savedInstanceState.containsKey(KEY_LOCATION)) { | ||||
| 			mapController.setCenter(savedInstanceState.getParcelable(KEY_LOCATION)); | ||||
| 		} | ||||
| 		if (savedInstanceState.containsKey(KEY_ZOOM_LEVEL)) { | ||||
| 			mapController.setZoom(savedInstanceState.getDouble(KEY_ZOOM_LEVEL)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	protected void setupMapView(final GeoPoint pos) { | ||||
| 		// Get map view and configure it. | ||||
| 		map = findViewById(R.id.map); | ||||
| 		map.setTileSource(tileSource()); | ||||
| 		map.setBuiltInZoomControls(false); | ||||
| 		map.setMultiTouchControls(true); | ||||
| 		map.setTilesScaledToDpi(getPreferences().getBoolean("scale_tiles_for_high_dpi", false)); | ||||
| 		mapController = map.getController(); | ||||
| 		mapController.setZoom(Config.Map.INITIAL_ZOOM_LEVEL); | ||||
| 		mapController.setCenter(pos); | ||||
| 	} | ||||
| 
 | ||||
| 	protected void gotoLoc() { | ||||
| 		gotoLoc(map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL); | ||||
| 	} | ||||
| 
 | ||||
| 	protected abstract void gotoLoc(final boolean setZoomLevel); | ||||
| 
 | ||||
| 	protected abstract void setMyLoc(final Location location); | ||||
| 
 | ||||
| 	protected void requestLocationUpdates() { | ||||
| 		if (!hasLocationFeature || locationManager == null) { | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		Log.d(Config.LOGTAG, "Requesting location updates..."); | ||||
| 		final Location lastKnownLocationGps; | ||||
| 		final Location lastKnownLocationNetwork; | ||||
| 
 | ||||
| 		try { | ||||
| 			if (locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER)) { | ||||
| 				lastKnownLocationGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); | ||||
| 
 | ||||
| 				if (lastKnownLocationGps != null) { | ||||
| 					setMyLoc(lastKnownLocationGps); | ||||
| 				} | ||||
| 				locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA, | ||||
| 						Config.Map.LOCATION_FIX_SPACE_DELTA, this); | ||||
| 			} else { | ||||
| 				lastKnownLocationGps = null; | ||||
| 			} | ||||
| 
 | ||||
| 			if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)) { | ||||
| 				lastKnownLocationNetwork = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); | ||||
| 				if (lastKnownLocationNetwork != null && LocationHelper.isBetterLocation(lastKnownLocationNetwork, | ||||
| 						lastKnownLocationGps)) { | ||||
| 					setMyLoc(lastKnownLocationNetwork); | ||||
| 				} | ||||
| 				locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA, | ||||
| 						Config.Map.LOCATION_FIX_SPACE_DELTA, this); | ||||
| 			} | ||||
| 
 | ||||
| 			// If something else is also querying for location more frequently than we are, the battery is already being | ||||
| 			// drained. Go ahead and use the existing locations as often as we can get them. | ||||
| 			if (locationManager.getAllProviders().contains(LocationManager.PASSIVE_PROVIDER)) { | ||||
| 				locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this); | ||||
| 			} | ||||
| 		} catch (final SecurityException ignored) { | ||||
| 			// Do nothing if the users device has no location providers. | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	protected void pauseLocationUpdates() throws SecurityException { | ||||
| 		if (locationManager != null) { | ||||
| 			locationManager.removeUpdates(this); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public boolean onOptionsItemSelected(final MenuItem item) { | ||||
| 		switch (item.getItemId()) { | ||||
| 			case android.R.id.home: | ||||
| 				finish(); | ||||
| 				return true; | ||||
| 		} | ||||
| 		return super.onOptionsItemSelected(item); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onPause() { | ||||
| 		super.onPause(); | ||||
| 		Configuration.getInstance().save(this, getPreferences()); | ||||
| 		map.onPause(); | ||||
| 		try { | ||||
| 			pauseLocationUpdates(); | ||||
| 		} catch (final SecurityException ignored) { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	protected abstract void updateUi(); | ||||
| 
 | ||||
| 	protected boolean mapAtInitialLoc() { | ||||
| 		return map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onResume() { | ||||
| 		super.onResume(); | ||||
| 		Configuration.getInstance().load(this, getPreferences()); | ||||
| 		map.onResume(); | ||||
| 		this.setMyLoc(null); | ||||
| 		requestLocationUpdates(); | ||||
| 		updateLocationMarkers(); | ||||
| 		updateUi(); | ||||
| 		map.setTileSource(tileSource()); | ||||
| 		map.setTilesScaledToDpi(getPreferences().getBoolean("scale_tiles_for_high_dpi", false)); | ||||
| 
 | ||||
| 		if (mapAtInitialLoc()) { | ||||
| 			gotoLoc(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@TargetApi(Build.VERSION_CODES.M) | ||||
| 	protected boolean hasLocationPermissions() { | ||||
| 		return (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || | ||||
| 				checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED); | ||||
| 	} | ||||
| 
 | ||||
| 	@TargetApi(Build.VERSION_CODES.M) | ||||
| 	protected void requestPermissions(final int request_code) { | ||||
| 		if (!hasLocationPermissions()) { | ||||
| 			requestPermissions( | ||||
| 					new String[]{ | ||||
| 							Manifest.permission.ACCESS_FINE_LOCATION, | ||||
| 							Manifest.permission.ACCESS_COARSE_LOCATION, | ||||
| 					}, | ||||
| 					request_code | ||||
| 			); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onRequestPermissionsResult(final int requestCode, | ||||
| 										   @NonNull final String[] permissions, | ||||
| 										   @NonNull final int[] grantResults) { | ||||
| 		super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
| 		for (int i = 0; i < grantResults.length; i++) { | ||||
| 			if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[i]) || | ||||
| 					Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[i])) { | ||||
| 				if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { | ||||
| 					requestLocationUpdates(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	protected SharedPreferences getPreferences() { | ||||
| 		return PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); | ||||
| 	} | ||||
| 
 | ||||
| 	@TargetApi(Build.VERSION_CODES.KITKAT) | ||||
| 	private boolean isLocationEnabledKitkat() { | ||||
| 		try { | ||||
| 			final int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE); | ||||
| 			return locationMode != Settings.Secure.LOCATION_MODE_OFF; | ||||
| 		} catch( final Settings.SettingNotFoundException e ){ | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@SuppressWarnings("deprecation") | ||||
| 	private boolean isLocationEnabledLegacy() { | ||||
| 		final String locationProviders = Settings.Secure.getString(getContentResolver(), | ||||
| 				Settings.Secure.LOCATION_PROVIDERS_ALLOWED); | ||||
| 		return !TextUtils.isEmpty(locationProviders); | ||||
| 	} | ||||
| 
 | ||||
| 	protected boolean isLocationEnabled() { | ||||
| 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||||
| 			return isLocationEnabledKitkat(); | ||||
| 		} else { | ||||
| 			return isLocationEnabledLegacy(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,248 @@ | |||
| package eu.siacs.conversations.ui; | ||||
| 
 | ||||
| import android.Manifest; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.location.Location; | ||||
| import android.location.LocationListener; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.design.widget.CoordinatorLayout; | ||||
| import android.support.design.widget.FloatingActionButton; | ||||
| import android.support.design.widget.Snackbar; | ||||
| import android.view.View; | ||||
| import android.widget.Button; | ||||
| 
 | ||||
| import org.osmdroid.api.IGeoPoint; | ||||
| import org.osmdroid.util.GeoPoint; | ||||
| 
 | ||||
| import eu.siacs.conversations.Config; | ||||
| import eu.siacs.conversations.R; | ||||
| import eu.siacs.conversations.ui.util.LocationHelper; | ||||
| import eu.siacs.conversations.ui.widget.Marker; | ||||
| import eu.siacs.conversations.ui.widget.MyLocation; | ||||
| 
 | ||||
| public class ShareLocationActivity extends LocationActivity implements LocationListener { | ||||
| 
 | ||||
| 	private Snackbar snackBar; | ||||
| 	private boolean marker_fixed_to_loc = false; | ||||
| 	private static final String KEY_FIXED_TO_LOC = "fixed_to_loc"; | ||||
| 	private Boolean noAskAgain = false; | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onSaveInstanceState(@NonNull final Bundle outState) { | ||||
| 		super.onSaveInstanceState(outState); | ||||
| 
 | ||||
| 		outState.putBoolean(KEY_FIXED_TO_LOC, marker_fixed_to_loc); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { | ||||
| 		super.onRestoreInstanceState(savedInstanceState); | ||||
| 
 | ||||
| 		if (savedInstanceState.containsKey(KEY_FIXED_TO_LOC)) { | ||||
| 			this.marker_fixed_to_loc = savedInstanceState.getBoolean(KEY_FIXED_TO_LOC); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onCreate(final Bundle savedInstanceState) { | ||||
| 		super.onCreate(savedInstanceState); | ||||
| 
 | ||||
| 		setContentView(R.layout.activity_share_location); | ||||
| 		setSupportActionBar(findViewById(R.id.toolbar)); | ||||
| 		configureActionBar(getSupportActionBar()); | ||||
| 		setupMapView(Config.Map.INITIAL_POS); | ||||
| 
 | ||||
| 		// Setup the cancel button | ||||
| 		final Button cancelButton = findViewById(R.id.cancel_button); | ||||
| 		cancelButton.setOnClickListener(view -> { | ||||
| 			setResult(RESULT_CANCELED); | ||||
| 			finish(); | ||||
| 		}); | ||||
| 
 | ||||
| 		final CoordinatorLayout snackBarCoordinator = findViewById(R.id.snackbarCoordinator); | ||||
| 		if (snackBarCoordinator != null) { | ||||
| 			this.snackBar = Snackbar.make(snackBarCoordinator, R.string.location_disabled, Snackbar.LENGTH_INDEFINITE); | ||||
| 			snackBar.setAction(R.string.enable, view -> { | ||||
| 				if (isLocationEnabledAndAllowed()) { | ||||
| 					updateUi(); | ||||
| 				} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasLocationPermissions()) { | ||||
| 					requestPermissions(REQUEST_CODE_SNACKBAR_PRESSED); | ||||
| 				} else if (!isLocationEnabled()) { | ||||
| 					startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		// Setup the share button | ||||
| 		final Button shareButton = findViewById(R.id.share_button); | ||||
| 		if (shareButton != null) { | ||||
| 			shareButton.setOnClickListener(view -> { | ||||
| 				final Intent result = new Intent(); | ||||
| 
 | ||||
| 				if (marker_fixed_to_loc && myLoc != null) { | ||||
| 					result.putExtra("latitude", myLoc.getLatitude()); | ||||
| 					result.putExtra("longitude", myLoc.getLongitude()); | ||||
| 					result.putExtra("altitude", myLoc.getAltitude()); | ||||
| 					result.putExtra("accuracy", (int) myLoc.getAccuracy()); | ||||
| 				} else { | ||||
| 					final IGeoPoint markerPoint = map.getMapCenter(); | ||||
| 					result.putExtra("latitude", markerPoint.getLatitude()); | ||||
| 					result.putExtra("longitude", markerPoint.getLongitude()); | ||||
| 				} | ||||
| 
 | ||||
| 				setResult(RESULT_OK, result); | ||||
| 				finish(); | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		this.marker_fixed_to_loc = isLocationEnabledAndAllowed(); | ||||
| 
 | ||||
| 		// Setup the fab button | ||||
| 		final FloatingActionButton toggleFixedMarkerButton = findViewById(R.id.fab); | ||||
| 		toggleFixedMarkerButton.setOnClickListener(view -> { | ||||
| 			if (!marker_fixed_to_loc) { | ||||
| 				if (!isLocationEnabled()) { | ||||
| 					startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); | ||||
| 				} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
| 					requestPermissions(REQUEST_CODE_FAB_PRESSED); | ||||
| 				} | ||||
| 			} | ||||
| 			toggleFixedLocation(); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onRequestPermissionsResult(final int requestCode, | ||||
| 										   @NonNull final String[] permissions, | ||||
| 										   @NonNull final int[] grantResults) { | ||||
| 		super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
| 
 | ||||
| 		if (grantResults.length > 0 && | ||||
| 				grantResults[0] != PackageManager.PERMISSION_GRANTED && | ||||
| 				Build.VERSION.SDK_INT >= 23 && | ||||
| 				permissions.length > 0 && | ||||
| 				( | ||||
| 						Manifest.permission.LOCATION_HARDWARE.equals(permissions[0]) || | ||||
| 								Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0]) || | ||||
| 								Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[0]) | ||||
| 				) && | ||||
| 				!shouldShowRequestPermissionRationale(permissions[0])) { | ||||
| 			noAskAgain = true; | ||||
| 		} | ||||
| 
 | ||||
| 		if (!noAskAgain && requestCode == REQUEST_CODE_SNACKBAR_PRESSED && !isLocationEnabled() && hasLocationPermissions()) { | ||||
| 			startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); | ||||
| 		} | ||||
| 		updateUi(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void gotoLoc(final boolean setZoomLevel) { | ||||
| 		if (this.myLoc != null && mapController != null) { | ||||
| 			if (setZoomLevel) { | ||||
| 				mapController.setZoom(Config.Map.FINAL_ZOOM_LEVEL); | ||||
| 			} | ||||
| 			mapController.animateTo(new GeoPoint(this.myLoc)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void setMyLoc(final Location location) { | ||||
| 		this.myLoc = location; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onPause() { | ||||
| 		super.onPause(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void updateLocationMarkers() { | ||||
| 		super.updateLocationMarkers(); | ||||
| 		if (this.myLoc != null) { | ||||
| 			this.map.getOverlays().add(new MyLocation(this, null, this.myLoc)); | ||||
| 			if (this.marker_fixed_to_loc) { | ||||
| 				map.getOverlays().add(new Marker(marker_icon, new GeoPoint(this.myLoc))); | ||||
| 			} else { | ||||
| 				map.getOverlays().add(new Marker(marker_icon)); | ||||
| 			} | ||||
| 		} else { | ||||
| 			map.getOverlays().add(new Marker(marker_icon)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onLocationChanged(final Location location) { | ||||
| 		if (this.myLoc == null) { | ||||
| 			this.marker_fixed_to_loc = true; | ||||
| 		} | ||||
| 		updateUi(); | ||||
| 		if (LocationHelper.isBetterLocation(location, this.myLoc)) { | ||||
| 			final Location oldLoc = this.myLoc; | ||||
| 			this.myLoc = location; | ||||
| 
 | ||||
| 			// Don't jump back to the users location if they're not moving (more or less). | ||||
| 			if (oldLoc == null || (this.marker_fixed_to_loc && this.myLoc.distanceTo(oldLoc) > 1)) { | ||||
| 				gotoLoc(); | ||||
| 			} | ||||
| 
 | ||||
| 			updateLocationMarkers(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onStatusChanged(final String provider, final int status, final Bundle extras) { | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onProviderEnabled(final String provider) { | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onProviderDisabled(final String provider) { | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private boolean isLocationEnabledAndAllowed() { | ||||
| 		return this.hasLocationFeature && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || this.hasLocationPermissions()) && this.isLocationEnabled(); | ||||
| 	} | ||||
| 
 | ||||
| 	private void toggleFixedLocation() { | ||||
| 		this.marker_fixed_to_loc = isLocationEnabledAndAllowed() && !this.marker_fixed_to_loc; | ||||
| 		if (this.marker_fixed_to_loc) { | ||||
| 			gotoLoc(false); | ||||
| 		} | ||||
| 		updateLocationMarkers(); | ||||
| 		updateUi(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void updateUi() { | ||||
| 		if (!hasLocationFeature || noAskAgain || isLocationEnabledAndAllowed()) { | ||||
| 			this.snackBar.dismiss(); | ||||
| 		} else { | ||||
| 			this.snackBar.show(); | ||||
| 		} | ||||
| 
 | ||||
| 		// Setup the fab button | ||||
| 		final FloatingActionButton fab = findViewById(R.id.fab); | ||||
| 		if (isLocationEnabledAndAllowed()) { | ||||
| 			fab.setVisibility(View.VISIBLE); | ||||
| 			runOnUiThread(() -> { | ||||
| 				fab.setImageResource(marker_fixed_to_loc ? R.drawable.ic_gps_fixed_white_24dp : | ||||
| 						R.drawable.ic_gps_not_fixed_white_24dp); | ||||
| 				fab.setContentDescription(getResources().getString( | ||||
| 						marker_fixed_to_loc ? R.string.action_unfix_from_location : R.string.action_fix_to_location | ||||
| 				)); | ||||
| 				fab.invalidate(); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			fab.setVisibility(View.GONE); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,234 @@ | |||
| package eu.siacs.conversations.ui; | ||||
| 
 | ||||
| import android.app.ActionBar; | ||||
| import android.content.ActivityNotFoundException; | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Intent; | ||||
| import android.location.Location; | ||||
| import android.location.LocationListener; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.design.widget.FloatingActionButton; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import org.osmdroid.util.GeoPoint; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import eu.siacs.conversations.Config; | ||||
| import eu.siacs.conversations.R; | ||||
| import eu.siacs.conversations.ui.util.LocationHelper; | ||||
| import eu.siacs.conversations.ui.util.UriHelper; | ||||
| import eu.siacs.conversations.ui.widget.Marker; | ||||
| import eu.siacs.conversations.ui.widget.MyLocation; | ||||
| 
 | ||||
| 
 | ||||
| public class ShowLocationActivity extends LocationActivity implements LocationListener { | ||||
| 
 | ||||
| 	private GeoPoint loc = Config.Map.INITIAL_POS; | ||||
| 	private FloatingActionButton navigationButton; | ||||
| 
 | ||||
| 
 | ||||
| 	private Uri createGeoUri() { | ||||
| 		return Uri.parse("geo:" + this.loc.getLatitude() + "," + this.loc.getLongitude()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onCreate(final Bundle savedInstanceState) { | ||||
| 		super.onCreate(savedInstanceState); | ||||
| 
 | ||||
| 		final ActionBar actionBar = getActionBar(); | ||||
| 		if (actionBar != null) { | ||||
| 			actionBar.setDisplayHomeAsUpEnabled(true); | ||||
| 		} | ||||
| 
 | ||||
| 		setContentView(R.layout.activity_show_location); | ||||
| 		setSupportActionBar(findViewById(R.id.toolbar)); | ||||
| 		configureActionBar(getSupportActionBar()); | ||||
| 		setupMapView(this.loc); | ||||
| 
 | ||||
| 		// Setup the fab button | ||||
| 		this.navigationButton = findViewById(R.id.fab); | ||||
| 		this.navigationButton.setOnClickListener(view -> startNavigation()); | ||||
| 
 | ||||
| 		final Intent intent = getIntent(); | ||||
| 		if (intent != null) { | ||||
| 			final String action = intent.getAction(); | ||||
| 			if (action == null) { | ||||
| 				return; | ||||
| 			} | ||||
| 			switch (action) { | ||||
| 				case "eu.siacs.conversations.location.show": | ||||
| 					if (intent.hasExtra("longitude") && intent.hasExtra("latitude")) { | ||||
| 						final double longitude = intent.getDoubleExtra("longitude", 0); | ||||
| 						final double latitude = intent.getDoubleExtra("latitude", 0); | ||||
| 						this.loc = new GeoPoint(latitude, longitude); | ||||
| 					} | ||||
| 					break; | ||||
| 				case Intent.ACTION_VIEW: | ||||
| 					final Uri geoUri = intent.getData(); | ||||
| 
 | ||||
| 					// Attempt to set zoom level if the geo URI specifies it | ||||
| 					if (geoUri != null) { | ||||
| 						final HashMap<String, String> query = UriHelper.parseQueryString(geoUri.getQuery()); | ||||
| 
 | ||||
| 						// Check for zoom level. | ||||
| 						final String z = query.get("z"); | ||||
| 						if (z != null) { | ||||
| 							try { | ||||
| 								mapController.setZoom(Double.valueOf(z)); | ||||
| 							} catch (final Exception ignored) { | ||||
| 							} | ||||
| 						} | ||||
| 
 | ||||
| 						// Check for the actual geo query. | ||||
| 						boolean posInQuery = false; | ||||
| 						final String q = query.get("q"); | ||||
| 						if (q != null) { | ||||
| 							final Pattern latlng = Pattern.compile("/^([-+]?[0-9]+(\\.[0-9]+)?),([-+]?[0-9]+(\\.[0-9]+)?)(\\(.*\\))?/"); | ||||
| 							final Matcher m = latlng.matcher(q); | ||||
| 							if (m.matches()) { | ||||
| 								try { | ||||
| 									this.loc = new GeoPoint(Double.valueOf(m.group(1)), Double.valueOf(m.group(3))); | ||||
| 									posInQuery = true; | ||||
| 								} catch (final Exception ignored) { | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 
 | ||||
| 						final String schemeSpecificPart = geoUri.getSchemeSpecificPart(); | ||||
| 						if (schemeSpecificPart != null && !schemeSpecificPart.isEmpty()) { | ||||
| 							try { | ||||
| 								final GeoPoint latlong = LocationHelper.parseLatLong(schemeSpecificPart); | ||||
| 								if (latlong != null && !posInQuery) { | ||||
| 									this.loc = latlong; | ||||
| 								} | ||||
| 							} catch (final NumberFormatException ignored) { | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 					break; | ||||
| 			} | ||||
| 			updateLocationMarkers(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void gotoLoc(final boolean setZoomLevel) { | ||||
| 		if (this.loc != null && mapController != null) { | ||||
| 			if (setZoomLevel) { | ||||
| 				mapController.setZoom(Config.Map.FINAL_ZOOM_LEVEL); | ||||
| 			} | ||||
| 			mapController.animateTo(new GeoPoint(this.loc)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onRequestPermissionsResult(final int requestCode, | ||||
| 										   @NonNull final String[] permissions, | ||||
| 										   @NonNull final int[] grantResults) { | ||||
| 		super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
| 		updateUi(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void setMyLoc(final Location location) { | ||||
| 		this.myLoc = location; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public boolean onCreateOptionsMenu(final Menu menu) { | ||||
| 		// Inflate the menu; this adds items to the action bar if it is present. | ||||
| 		getMenuInflater().inflate(R.menu.menu_show_location, menu); | ||||
| 		updateUi(); | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void updateLocationMarkers() { | ||||
| 		super.updateLocationMarkers(); | ||||
| 		if (this.myLoc != null) { | ||||
| 			this.map.getOverlays().add(new MyLocation(this, null, this.myLoc)); | ||||
| 		} | ||||
| 		this.map.getOverlays().add(new Marker(this.marker_icon, this.loc)); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onPause() { | ||||
| 		super.onPause(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public boolean onOptionsItemSelected(final MenuItem item) { | ||||
| 		switch (item.getItemId()) { | ||||
| 			case R.id.action_copy_location: | ||||
| 				final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); | ||||
| 				if (clipboard != null) { | ||||
| 					final ClipData clip = ClipData.newPlainText("location", createGeoUri().toString()); | ||||
| 					clipboard.setPrimaryClip(clip); | ||||
| 				} | ||||
| 				return true; | ||||
| 			case R.id.action_share_location: | ||||
| 				final Intent shareIntent = new Intent(); | ||||
| 				shareIntent.setAction(Intent.ACTION_SEND); | ||||
| 				shareIntent.putExtra(Intent.EXTRA_TEXT, createGeoUri().toString()); | ||||
| 				shareIntent.setType("text/plain"); | ||||
| 				try { | ||||
| 					startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); | ||||
| 				} catch (final ActivityNotFoundException e) { | ||||
| 					//This should happen only on faulty androids because normally chooser is always available | ||||
| 					Toast.makeText(this, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); | ||||
| 				} | ||||
| 				return true; | ||||
| 		} | ||||
| 		return super.onOptionsItemSelected(item); | ||||
| 	} | ||||
| 
 | ||||
| 	private void startNavigation() { | ||||
| 		startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse( | ||||
| 				"google.navigation:q=" + | ||||
| 						String.valueOf(this.loc.getLatitude()) + "," + String.valueOf(this.loc.getLongitude()) | ||||
| 		))); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void updateUi() { | ||||
| 		final Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("google.navigation:q=0,0")); | ||||
| 		final ComponentName component = i.resolveActivity(getPackageManager()); | ||||
| 		if (this.navigationButton != null) { | ||||
| 			this.navigationButton.setVisibility(component == null ? View.GONE : View.VISIBLE); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onLocationChanged(final Location location) { | ||||
| 		if (LocationHelper.isBetterLocation(location, this.myLoc)) { | ||||
| 			this.myLoc = location; | ||||
| 			updateLocationMarkers(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onStatusChanged(final String provider, final int status, final Bundle extras) { | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onProviderEnabled(final String provider) { | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onProviderDisabled(final String provider) { | ||||
| 
 | ||||
| 	} | ||||
| } | ||||
|  | @ -165,4 +165,4 @@ public class UriHandlerActivity extends AppCompatActivity { | |||
| 		} | ||||
| 		finish(); | ||||
| 	} | ||||
| } | ||||
| } | ||||
|  | @ -3,9 +3,6 @@ package eu.siacs.conversations.ui; | |||
| import android.Manifest; | ||||
| import android.annotation.SuppressLint; | ||||
| import android.annotation.TargetApi; | ||||
| import android.support.v7.app.ActionBar; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.support.v7.app.AlertDialog.Builder; | ||||
| import android.app.PendingIntent; | ||||
| import android.content.ActivityNotFoundException; | ||||
| import android.content.ClipData; | ||||
|  | @ -37,7 +34,8 @@ import android.os.PowerManager; | |||
| import android.os.SystemClock; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.support.v7.app.AlertDialog.Builder; | ||||
| import android.support.v7.app.AppCompatDelegate; | ||||
| import android.text.InputType; | ||||
| import android.util.DisplayMetrics; | ||||
|  | @ -76,7 +74,7 @@ import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; | |||
| import eu.siacs.conversations.xmpp.OnUpdateBlocklist; | ||||
| import rocks.xmpp.addr.Jid; | ||||
| 
 | ||||
| public abstract class XmppActivity extends AppCompatActivity { | ||||
| public abstract class XmppActivity extends ActionBarActivity { | ||||
| 
 | ||||
| 	public static final String EXTRA_ACCOUNT = "account"; | ||||
| 	protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; | ||||
|  | @ -610,13 +608,6 @@ public abstract class XmppActivity extends AppCompatActivity { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public static void configureActionBar(ActionBar actionBar) { | ||||
| 		if (actionBar != null) { | ||||
| 			actionBar.setHomeButtonEnabled(true); | ||||
| 			actionBar.setDisplayHomeAsUpEnabled(true); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	protected boolean noAccountUsesPgp() { | ||||
| 		if (!hasPgp()) { | ||||
| 			return true; | ||||
|  |  | |||
|  | @ -0,0 +1,72 @@ | |||
| package eu.siacs.conversations.ui.util; | ||||
| 
 | ||||
| import android.location.Location; | ||||
| 
 | ||||
| import org.osmdroid.util.GeoPoint; | ||||
| 
 | ||||
| import eu.siacs.conversations.Config; | ||||
| 
 | ||||
| public final class LocationHelper { | ||||
| 	/** | ||||
| 	 * Parses a lat long string in the form "lat,long". | ||||
| 	 * | ||||
| 	 * @param latlong A string in the form "lat,long" | ||||
| 	 * @return A GeoPoint representing the lat,long string. | ||||
| 	 * @throws NumberFormatException If an invalid lat or long is specified. | ||||
| 	 */ | ||||
| 	public static GeoPoint parseLatLong(final String latlong) throws NumberFormatException { | ||||
| 		if (latlong == null || latlong.isEmpty()) { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		final String[] parts = latlong.split(","); | ||||
| 		if (parts[1].contains("?")) { | ||||
| 			parts[1] = parts[1].substring(0, parts[1].indexOf("?")); | ||||
| 		} | ||||
| 		return new GeoPoint(Double.valueOf(parts[0]), Double.valueOf(parts[1])); | ||||
| 	} | ||||
| 
 | ||||
| 	private static boolean isSameProvider(final String provider1, final String provider2) { | ||||
| 		if (provider1 == null) { | ||||
| 			return provider2 == null; | ||||
| 		} | ||||
| 		return provider1.equals(provider2); | ||||
| 	} | ||||
| 
 | ||||
| 	public static boolean isBetterLocation(final Location location, final Location prevLoc) { | ||||
| 		if (prevLoc == null) { | ||||
| 			return true; | ||||
| 		} | ||||
| 
 | ||||
| 		// Check whether the new location fix is newer or older | ||||
| 		final long timeDelta = location.getTime() - prevLoc.getTime(); | ||||
| 		final boolean isSignificantlyNewer = timeDelta > Config.Map.LOCATION_FIX_SIGNIFICANT_TIME_DELTA; | ||||
| 		final boolean isSignificantlyOlder = timeDelta < -Config.Map.LOCATION_FIX_SIGNIFICANT_TIME_DELTA; | ||||
| 		final boolean isNewer = timeDelta > 0; | ||||
| 
 | ||||
| 		if (isSignificantlyNewer) { | ||||
| 			return true; | ||||
| 		} else if (isSignificantlyOlder) { | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		// Check whether the new location fix is more or less accurate | ||||
| 		final int accuracyDelta = (int) (location.getAccuracy() - prevLoc.getAccuracy()); | ||||
| 		final boolean isLessAccurate = accuracyDelta > 0; | ||||
| 		final boolean isMoreAccurate = accuracyDelta < 0; | ||||
| 		final boolean isSignificantlyLessAccurate = accuracyDelta > 200; | ||||
| 
 | ||||
| 		// Check if the old and new location are from the same provider | ||||
| 		final boolean isFromSameProvider = isSameProvider(location.getProvider(), prevLoc.getProvider()); | ||||
| 
 | ||||
| 		// Determine location quality using a combination of timeliness and accuracy | ||||
| 		if (isMoreAccurate) { | ||||
| 			return true; | ||||
| 		} else if (isNewer && !isLessAccurate) { | ||||
| 			return true; | ||||
| 		} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,30 @@ | |||
| package eu.siacs.conversations.ui.util; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| 
 | ||||
| /** | ||||
|  * Helper methods for parsing URI's. | ||||
|  */ | ||||
| public final class UriHelper { | ||||
| 	/** | ||||
| 	 * Parses a query string into a hashmap. | ||||
| 	 * | ||||
| 	 * @param q The query string to split. | ||||
| 	 * @return A hashmap containing the key-value pairs from the query string. | ||||
| 	 */ | ||||
| 	public static HashMap<String, String> parseQueryString(final String q) { | ||||
| 		if (q == null || q.isEmpty()) { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		final String[] query = q.split("&"); | ||||
| 		// TODO: Look up the HashMap implementation and figure out what the load factor is and make sure we're not reallocating here. | ||||
| 		final HashMap<String, String> queryMap = new HashMap<>(query.length); | ||||
| 		for (final String param : query) { | ||||
| 			final String[] pair = param.split("="); | ||||
| 			queryMap.put(pair[0], pair.length == 2 && !pair[1].isEmpty() ? pair[1] : null); | ||||
| 		} | ||||
| 
 | ||||
| 		return queryMap; | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,52 @@ | |||
| package eu.siacs.conversations.ui.widget; | ||||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Point; | ||||
| 
 | ||||
| import org.osmdroid.util.GeoPoint; | ||||
| import org.osmdroid.views.MapView; | ||||
| import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay; | ||||
| 
 | ||||
| /** | ||||
|  * An immutable marker overlay. | ||||
|  */ | ||||
| public class Marker extends SimpleLocationOverlay { | ||||
| 	private final GeoPoint position; | ||||
| 	private final Bitmap icon; | ||||
| 	private final Point mapPoint; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a marker overlay which will be drawn at the current Geographical position. | ||||
| 	 * @param icon A bitmap icon for the marker | ||||
| 	 * @param position The geographic position where the marker will be drawn (if it is inside the view) | ||||
| 	 */ | ||||
| 	public Marker(final Bitmap icon, final GeoPoint position) { | ||||
| 		super(icon); | ||||
| 		this.icon = icon; | ||||
| 		this.position = position; | ||||
| 		this.mapPoint = new Point(); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a marker overlay which will be drawn centered in the view. | ||||
| 	 * @param icon A bitmap icon for the marker | ||||
| 	 */ | ||||
| 	public Marker(final Bitmap icon) { | ||||
| 		this(icon, null); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void draw(final Canvas c, final MapView view, final boolean shadow) { | ||||
| 		super.draw(c, view, shadow); | ||||
| 
 | ||||
| 		// If no position was set for the marker, draw it centered in the view. | ||||
| 		view.getProjection().toPixels(this.position == null ? view.getMapCenter() : position, mapPoint); | ||||
| 
 | ||||
| 		c.drawBitmap(icon, | ||||
| 				mapPoint.x - icon.getWidth() / 2, | ||||
| 				mapPoint.y - icon.getHeight(), | ||||
| 				null); | ||||
| 
 | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,65 @@ | |||
| package eu.siacs.conversations.ui.widget; | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Context; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Point; | ||||
| import android.location.Location; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import org.osmdroid.util.GeoPoint; | ||||
| import org.osmdroid.views.MapView; | ||||
| import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay; | ||||
| 
 | ||||
| import eu.siacs.conversations.Config; | ||||
| import eu.siacs.conversations.R; | ||||
| import microsoft.mappoint.TileSystem; | ||||
| 
 | ||||
| public class MyLocation extends SimpleLocationOverlay { | ||||
| 	private final GeoPoint position; | ||||
| 	private final float accuracy; | ||||
| 	private final Point mapCenterPoint; | ||||
| 	private final Paint fill; | ||||
| 	private final Paint outline; | ||||
| 
 | ||||
| 	@TargetApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| 	private int getColor(final Context ctx) { | ||||
| 		final int accent; | ||||
| 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
| 			accent = ctx.getResources().getColor(R.color.accent, ctx.getTheme()); | ||||
| 		} else { | ||||
| 			//noinspection deprecation | ||||
| 			accent = ctx.getResources().getColor(R.color.accent); | ||||
| 		} | ||||
| 		return accent; | ||||
| 	} | ||||
| 
 | ||||
| 	public MyLocation(final Context ctx, final Bitmap icon, final Location position) { | ||||
| 		super(icon); | ||||
| 		this.mapCenterPoint = new Point(); | ||||
| 		this.fill = new Paint(Paint.ANTI_ALIAS_FLAG); | ||||
| 		final int accent = this.getColor(ctx); | ||||
| 		fill.setColor(accent); | ||||
| 		fill.setStyle(Paint.Style.FILL); | ||||
| 		this.outline = new Paint(Paint.ANTI_ALIAS_FLAG); | ||||
| 		outline.setColor(accent); | ||||
| 		outline.setAlpha(50); | ||||
| 		outline.setStyle(Paint.Style.FILL); | ||||
| 		this.position = new GeoPoint(position); | ||||
| 		this.accuracy = position.getAccuracy(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void draw(final Canvas c, final MapView view, final boolean shadow) { | ||||
| 		super.draw(c, view, shadow); | ||||
| 
 | ||||
| 		view.getProjection().toPixels(position, mapCenterPoint); | ||||
| 		c.drawCircle(mapCenterPoint.x, mapCenterPoint.y, | ||||
| 				Math.max(Config.Map.MY_LOCATION_INDICATOR_SIZE + Config.Map.MY_LOCATION_INDICATOR_OUTLINE_SIZE, | ||||
| 						accuracy / (float) TileSystem.GroundResolution(position.getLatitude(), view.getZoomLevel()) | ||||
| 				), this.outline); | ||||
| 		c.drawCircle(mapCenterPoint.x, mapCenterPoint.y, Config.Map.MY_LOCATION_INDICATOR_SIZE, this.fill); | ||||
| 	} | ||||
| } | ||||
| After Width: | Height: | Size: 233 B | 
| After Width: | Height: | Size: 252 B | 
| After Width: | Height: | Size: 549 B | 
| After Width: | Height: | Size: 546 B | 
| After Width: | Height: | Size: 472 B | 
| After Width: | Height: | Size: 468 B | 
| After Width: | Height: | Size: 3.5 KiB | 
| After Width: | Height: | Size: 181 B | 
| After Width: | Height: | Size: 191 B | 
| After Width: | Height: | Size: 341 B | 
| After Width: | Height: | Size: 350 B | 
| After Width: | Height: | Size: 295 B | 
| After Width: | Height: | Size: 298 B | 
| After Width: | Height: | Size: 2.2 KiB | 
| After Width: | Height: | Size: 274 B | 
| After Width: | Height: | Size: 307 B | 
| After Width: | Height: | Size: 660 B | 
| After Width: | Height: | Size: 687 B | 
| After Width: | Height: | Size: 561 B | 
| After Width: | Height: | Size: 577 B | 
| After Width: | Height: | Size: 4.7 KiB | 
| After Width: | Height: | Size: 393 B | 
| After Width: | Height: | Size: 444 B | 
| After Width: | Height: | Size: 976 B | 
| After Width: | Height: | Size: 1012 B | 
| After Width: | Height: | Size: 803 B | 
| After Width: | Height: | Size: 830 B | 
| After Width: | Height: | Size: 7.3 KiB | 
| After Width: | Height: | Size: 491 B | 
| After Width: | Height: | Size: 554 B | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 9.7 KiB | 
|  | @ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FF000000" | ||||
|         android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z"/> | ||||
| </vector> | ||||
|  | @ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FF000000" | ||||
|         android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/> | ||||
| </vector> | ||||
|  | @ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FF000000" | ||||
|         android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/> | ||||
| </vector> | ||||
|  | @ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <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> | ||||
|  | @ -0,0 +1,66 @@ | |||
| <android.support.design.widget.CoordinatorLayout | ||||
|     android:id="@+id/snackbarCoordinator" | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
|     <RelativeLayout | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         tools:context=".ui.ShareLocationActivity"> | ||||
| 
 | ||||
|         <include layout="@layout/toolbar" /> | ||||
| 
 | ||||
|         <org.osmdroid.views.MapView android:id="@+id/map" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent" | ||||
|             android:layout_above="@+id/button_bar"/> | ||||
| 
 | ||||
|         <LinearLayout | ||||
|             android:id="@+id/button_bar" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_alignParentBottom="true" | ||||
|             android:layout_alignParentLeft="true" | ||||
|             android:layout_alignParentRight="true" | ||||
|             tools:ignore="RtlHardcoded"> | ||||
| 
 | ||||
|             <Button | ||||
|                 android:id="@+id/cancel_button" | ||||
|                 style="?android:attr/borderlessButtonStyle" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_weight="1" | ||||
|                 android:text="@string/cancel" | ||||
|                 android:textAppearance="@style/TextAppearance.Conversations.Body1"/> | ||||
| 
 | ||||
|             <View | ||||
|                 android:layout_width="1dp" | ||||
|                 android:layout_height="match_parent" | ||||
|                 android:layout_marginBottom="7dp" | ||||
|                 android:layout_marginTop="7dp" | ||||
|                 android:background="@color/accent"/> | ||||
| 
 | ||||
|             <Button | ||||
|                 android:id="@+id/share_button" | ||||
|                 style="?android:attr/borderlessButtonStyle" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_weight="1" | ||||
|                 android:text="@string/share_with" | ||||
|                 android:textAppearance="@style/TextAppearance.Conversations.Body1"/> | ||||
|         </LinearLayout> | ||||
| 
 | ||||
|         <android.support.design.widget.FloatingActionButton | ||||
|             android:id="@+id/fab" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="end|bottom" | ||||
|             android:src="?attr/icon_gps_fixed" | ||||
|             android:layout_alignParentEnd="true" | ||||
|             android:layout_above="@+id/button_bar" | ||||
|             android:contentDescription="@string/action_unfix_from_location" | ||||
|             android:layout_margin="16dp" /> | ||||
| 
 | ||||
|     </RelativeLayout> | ||||
| </android.support.design.widget.CoordinatorLayout> | ||||
|  | @ -0,0 +1,25 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     tools:context=".ui.ShowLocationActivity"> | ||||
| 
 | ||||
|     <include layout="@layout/toolbar" /> | ||||
| 
 | ||||
|     <org.osmdroid.views.MapView android:id="@+id/map" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="fill_parent"/> | ||||
| 
 | ||||
|     <android.support.design.widget.FloatingActionButton | ||||
|         android:id="@+id/fab" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="end|bottom" | ||||
|         android:src="?attr/icon_directions" | ||||
|         android:tint="@color/white" | ||||
|         android:layout_alignParentEnd="true" | ||||
|         android:contentDescription="@string/action_unfix_from_location" | ||||
|         android:layout_margin="16dp" | ||||
|         android:layout_alignParentBottom="true"/> | ||||
| </RelativeLayout> | ||||
|  | @ -0,0 +1,14 @@ | |||
| <menu | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <item android:id="@+id/action_share_location" | ||||
|         app:showAsAction="ifRoom" | ||||
|         android:showAsAction="ifRoom" | ||||
|         android:title="@string/action_share_location" | ||||
|         android:icon="?attr/icon_share"/> | ||||
|     <item android:id="@+id/action_copy_location" | ||||
|         android:title="@string/action_copy_location" | ||||
|         android:icon="?attr/icon_copy_bar" | ||||
|         app:showAsAction="ifRoom" | ||||
|         android:showAsAction="ifRoom"/> | ||||
| </menu> | ||||
|  | @ -57,5 +57,8 @@ | |||
| 			\n\nhttps://github.com/vinc3m1/RoundedImageView\n(Apache License, Version 2.0) | ||||
| 			\n\nhttps://github.com/jdamcd/android-crop\n(Apache License, Version 2.0) | ||||
| 			\n\nhttps://github.com/zxing/zxing\n(Apache License, Version 2.0) | ||||
| 			\n\nhttps://github.com/osmdroid/osmdroid\n(Apache License, Version 2.0) | ||||
| 			\n\n\nMaps | ||||
| 			\n\nMaps by Open Street Map (https://www.openstreetmap.org). Copyright restrictions may apply. | ||||
| 	</string> | ||||
| </resources> | ||||
|  |  | |||
|  | @ -66,6 +66,11 @@ | |||
|     <attr name="icon_enable_undecided_device" format="reference"/> | ||||
|     <attr name="icon_scroll_down" format="reference"/> | ||||
| 
 | ||||
|     <attr name="icon_gps_not_fixed" format="reference"/> | ||||
|     <attr name="icon_gps_fixed" format="reference"/> | ||||
|     <attr name="icon_directions" format="reference"/> | ||||
|     <attr name="icon_copy_bar" format="reference"/> | ||||
| 
 | ||||
|     <attr name="icon_notifications" format="reference"/> | ||||
|     <attr name="icon_notifications_off" format="reference"/> | ||||
|     <attr name="icon_notifications_paused" format="reference"/> | ||||
|  |  | |||
|  | @ -690,4 +690,12 @@ | |||
|     <string name="large">Large</string> | ||||
|     <string name="not_encrypted_for_this_device">Message was not encrypted for this device.</string> | ||||
|     <string name="undo">undo</string> | ||||
|     <string name="location_disabled">Location sharing is disabled</string> | ||||
|     <string name="action_fix_to_location">Fix position</string> | ||||
|     <string name="action_unfix_from_location">Unfix position</string> | ||||
|     <string name="action_copy_location">Copy Location</string> | ||||
|     <string name="action_share_location">Share Location</string> | ||||
|     <string name="action_directions">Directions</string> | ||||
|     <string name="title_activity_share_location">Share location</string> | ||||
|     <string name="title_activity_show_location">Show location</string> | ||||
| </resources> | ||||
|  |  | |||
|  | @ -79,6 +79,11 @@ | |||
|         <item type="reference" name="icon_scan_qr_code">@drawable/ic_qr_code_scan_white_24dp</item> | ||||
|         <item type="reference" name="icon_scroll_down">@drawable/ic_scroll_to_end_black</item> | ||||
| 
 | ||||
|         <item type="reference" name="icon_gps_not_fixed">@drawable/ic_gps_not_fixed_black_24dp</item> | ||||
|         <item type="reference" name="icon_gps_fixed">@drawable/ic_gps_fixed_black_24dp</item> | ||||
|         <item type="reference" name="icon_directions">@drawable/ic_directions_black_24dp</item> | ||||
|         <item type="reference" name="icon_copy_bar">@drawable/ic_content_copy_white_24dp</item> | ||||
| 
 | ||||
|         <item type="reference" name="icon_notifications">@drawable/ic_notifications_black_24dp</item> | ||||
|         <item type="reference" name="icon_notifications_off">@drawable/ic_notifications_off_black_24dp</item> | ||||
|         <item type="reference" name="icon_notifications_paused">@drawable/ic_notifications_paused_black_24dp</item> | ||||
|  | @ -164,6 +169,11 @@ | |||
|         <item type="reference" name="icon_scan_qr_code">@drawable/ic_qr_code_scan_white_24dp</item> | ||||
|         <item type="reference" name="icon_scroll_down">@drawable/ic_scroll_to_end_white</item> | ||||
| 
 | ||||
|         <item type="reference" name="icon_gps_not_fixed">@drawable/ic_gps_not_fixed_white_24dp</item> | ||||
|         <item type="reference" name="icon_gps_fixed">@drawable/ic_gps_fixed_white_24dp</item> | ||||
|         <item type="reference" name="icon_directions">@drawable/ic_directions_white_24dp</item> | ||||
|         <item type="reference" name="icon_copy_bar">@drawable/ic_content_copy_white_24dp</item> | ||||
| 
 | ||||
|         <item type="reference" name="icon_notifications">@drawable/ic_notifications_white_24dp</item> | ||||
|         <item type="reference" name="icon_notifications_off">@drawable/ic_notifications_off_white_24dp</item> | ||||
|         <item type="reference" name="icon_notifications_paused">@drawable/ic_notifications_paused_white_24dp</item> | ||||
|  |  | |||
 Daniel Gultsch
						Daniel Gultsch