Merge branch 'mapping' of https://github.com/SamWhited/Conversations into SamWhited-mapping

This commit is contained in:
Daniel Gultsch 2018-04-21 16:57:53 +02:00
commit ee855ab560
61 changed files with 1352 additions and 17 deletions

110
art/marker.svg Normal file
View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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">

View file

@ -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
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}

View file

@ -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) {
}
}

View file

@ -165,4 +165,4 @@ public class UriHandlerActivity extends AppCompatActivity {
}
finish();
}
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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"/>

View file

@ -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>

View file

@ -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>