1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-28 19:45:50 +01:00

Add GPS fragment to Sport Activity detail

add GPX parser
fix possible swiping issue after device rotation
use window background color for screenshots
This commit is contained in:
vanous 2020-10-22 06:32:21 +02:00
parent 9d6ac2b985
commit 4d42e169b9
9 changed files with 345 additions and 17 deletions

View File

@ -1003,6 +1003,13 @@ public class GBApplication extends Application {
return typedValue.data;
}
public static int getWindowBackgroundColor(Context context) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(android.R.attr.windowBackground, typedValue, true);
return typedValue.data;
}
public static Prefs getPrefs() {
return prefs;
}

View File

@ -0,0 +1,142 @@
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Dikay900, Pavel Elagin
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
import nodomain.freeyourgadget.gadgetbridge.util.GpxParser;
import static android.graphics.Bitmap.createBitmap;
public class ActivitySummariesGpsFragment extends AbstractGBFragment {
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesGpsFragment.class);
private ImageView gpsView;
private int CANVAS_SIZE = 360;
private File inputFile;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_gps, container, false);
gpsView = rootView.findViewById(R.id.activitygpsview);
if (inputFile != null) {
processInBackgroundThread();
}
return rootView;
}
public void set_data(File inputFile) {
this.inputFile = inputFile;
if (gpsView != null) { //first fragment inflate is AFTER this is called
processInBackgroundThread();
}
}
private void processInBackgroundThread() {
final Canvas canvas = createCanvas(gpsView);
new Thread(new Runnable() {
@Override
public void run() {
GpxParser gpxParser = null;
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(inputFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (inputStream != null) {
gpxParser = new GpxParser(inputStream);
}
if (gpxParser != null) {
drawTrack(canvas, gpxParser.getPoints());
}
}
}).start();
}
private void drawTrack(Canvas canvas, List<GPSCoordinate> trackPoints) {
double maxLat = (Collections.max(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
double minLat = (Collections.min(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
double maxLon = (Collections.max(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
double minLon = (Collections.min(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
double maxAlt = (Collections.max(trackPoints, new GPSCoordinate.compareElevation())).getAltitude();
double minAlt = (Collections.min(trackPoints, new GPSCoordinate.compareElevation())).getAltitude();
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(1);
paint.setColor(getResources().getColor(R.color.chart_activity_light));
for (GPSCoordinate p : trackPoints) {
float lat = (float) ((p.getLatitude() - minLat) / (maxLat - minLat));
float lon = (float) ((p.getLongitude() - minLon) / (maxLon - minLon));
float alt = (float) ((p.getAltitude() - minAlt) / (maxAlt - minAlt));
paint.setStrokeWidth(1 + alt); //make thicker with higher altitude, we could do more here
canvas.drawPoint(CANVAS_SIZE * lat, CANVAS_SIZE * lon, paint);
}
}
private Canvas createCanvas(ImageView imageView) {
Bitmap bitmap = createBitmap(CANVAS_SIZE, CANVAS_SIZE, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(GBApplication.getWindowBackgroundColor(getActivity()));
//frame around, but it doesn't look so nice
/*
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(0);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(getResources().getColor(R.color.chart_activity_light));
canvas.drawRect(0,0,360,360,paint);
*/
imageView.setImageBitmap(bitmap);
return canvas;
}
@Nullable
@Override
protected CharSequence getTitle() {
return null;
}
}

View File

@ -26,7 +26,6 @@ import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
@ -97,11 +96,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
public static Bitmap getScreenShot(View view, int height, int width, Context context) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
if (GBApplication.isDarkThemeEnabled()) {
canvas.drawColor(GBApplication.getBackgroundColor(context));
} else {
canvas.drawColor(Color.WHITE);
}
canvas.drawColor(GBApplication.getWindowBackgroundColor(context));
view.draw(canvas);
return bitmap;
}
@ -153,10 +148,12 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
R.anim.bounceright);
final ActivitySummariesChartFragment activitySummariesChartFragment = new ActivitySummariesChartFragment();
final ActivitySummariesGpsFragment activitySummariesGpsFragment = new ActivitySummariesGpsFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentHolder, activitySummariesChartFragment)
.replace(R.id.chartsFragmentHolder, activitySummariesChartFragment)
.replace(R.id.gpsFragmentHolder, activitySummariesGpsFragment)
.commit();
layout.setOnTouchListener(new SwipeEvents(this) {
@ -168,6 +165,13 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
makeSummaryHeader(newItem);
makeSummaryContent(newItem);
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000);
if (get_gpx_file() != null) {
showCanvas();
activitySummariesGpsFragment.set_data(get_gpx_file());
} else {
hideCanvas();
}
layout.startAnimation(animFadeRight);
show_hide_gpx_menu();
} else {
@ -183,6 +187,14 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
makeSummaryHeader(newItem);
makeSummaryContent(newItem);
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000);
if (get_gpx_file() != null) {
showCanvas();
activitySummariesGpsFragment.set_data(get_gpx_file());
} else {
hideCanvas();
}
layout.startAnimation(animFadeLeft);
show_hide_gpx_menu();
} else {
@ -196,6 +208,13 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
makeSummaryHeader(currentItem);
makeSummaryContent(currentItem);
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000);
if (get_gpx_file() != null) {
showCanvas();
activitySummariesGpsFragment.set_data(get_gpx_file());
} else {
hideCanvas();
}
}
@ -464,6 +483,34 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
}
}
private void showCanvas() {
View gpsView = findViewById(R.id.gpsFragmentHolder);
ViewGroup.LayoutParams params = gpsView.getLayoutParams();
params.height = (int) (300 * getApplicationContext().getResources().getDisplayMetrics().density);
gpsView.setLayoutParams(params);
}
private void hideCanvas() {
View gpsView = findViewById(R.id.gpsFragmentHolder);
ViewGroup.LayoutParams params = gpsView.getLayoutParams();
params.height = 0;
gpsView.setLayoutParams(params);
}
private File get_gpx_file() {
final String gpxTrack = currentItem.getGpxTrack();
if (gpxTrack != null) {
File file = new File(gpxTrack);
if (file.exists()) {
return file;
} else {
return null;
}
}
return null;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);

View File

@ -18,6 +18,7 @@ package nodomain.freeyourgadget.gadgetbridge.model;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Comparator;
public final class GPSCoordinate {
private final double latitude;
@ -78,4 +79,26 @@ public final class GPSCoordinate {
public String toString() {
return "lon: " + formatLocation(longitude) + ", lat: " + formatLocation(latitude) + ", alt: " + formatLocation(altitude) + "m";
}
public static class compareLatitude implements Comparator<GPSCoordinate> {
@Override
public int compare(GPSCoordinate trkPt1, GPSCoordinate trkPt2) {
return Double.compare(trkPt1.getLatitude(), trkPt2.getLatitude());
}
}
public static class compareLongitude implements Comparator<GPSCoordinate> {
@Override
public int compare(GPSCoordinate trkPt1, GPSCoordinate trkPt2) {
return Double.compare(trkPt1.getLongitude(), trkPt2.getLongitude());
}
}
public static class compareElevation implements Comparator<GPSCoordinate> {
@Override
public int compare(GPSCoordinate trkPt1, GPSCoordinate trkPt2) {
return Double.compare(trkPt1.getAltitude(), trkPt2.getAltitude());
}
}
}

View File

@ -0,0 +1,78 @@
package nodomain.freeyourgadget.gadgetbridge.util;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
public class GpxParser {
private XmlPullParser parser;
private List<GPSCoordinate> points;
private int eventType;
public GpxParser(InputStream stream) {
points = new ArrayList<>();
try {
parser = createXmlParser(stream);
parseGpx();
} catch (Exception e) {
e.printStackTrace();
}
}
private static XmlPullParser createXmlParser(InputStream stream) throws XmlPullParserException {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser parser = factory.newPullParser();
parser.setInput(stream, null);
return parser;
}
private void parseGpx() throws XmlPullParserException, IOException {
eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.getName().equals("trkpt"))
points.add(parsePoint(parser));
else
eventType = parser.next();
}
}
private double parseElevation(XmlPullParser parser) throws XmlPullParserException, IOException {
String eleString = "";
while (eventType != XmlPullParser.END_TAG) {
if (eventType == XmlPullParser.TEXT) {
eleString = parser.getText();
}
eventType = parser.next();
}
return Double.parseDouble(eleString);
}
private GPSCoordinate parsePoint(XmlPullParser parser) throws XmlPullParserException, IOException {
double lat;
double lon;
double ele = 0;
String latString = parser.getAttributeValue(null, "lat");
String lonString = parser.getAttributeValue(null, "lon");
lat = latString != null ? Double.parseDouble(latString) : 0;
lon = lonString != null ? Double.parseDouble(lonString) : 0;
while (eventType != XmlPullParser.END_TAG) {
if (parser.getEventType() == XmlPullParser.START_TAG && parser.getName().equals("ele")) {
ele = parseElevation(parser);
}
eventType = parser.next();
}
return new GPSCoordinate(lat, lon, ele);
}
public List<GPSCoordinate> getPoints() {
return points;
}
}

View File

@ -40,6 +40,9 @@ public class SwipeEvents implements View.OnTouchListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (e1 == null || e2 == null){
return false;
}
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {

View File

@ -2,7 +2,8 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_summary_detail_scroll_layout"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical">
<LinearLayout
@ -141,10 +142,7 @@
android:layout_weight="1"
android:gravity="end" />
</TableRow>
</TableLayout>
</LinearLayout>
</LinearLayout>
@ -166,13 +164,14 @@
android:layout_height="wrap_content" />
<FrameLayout
android:id="@+id/fragmentHolder"
android:id="@+id/chartsFragmentHolder"
android:layout_width="match_parent"
android:layout_height="300dp">
</FrameLayout>
android:layout_height="300dp"></FrameLayout>
<FrameLayout
android:id="@+id/gpsFragmentHolder"
android:layout_width="match_parent"
android:layout_height="300dp"></FrameLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@ -0,0 +1,28 @@
<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="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:gravity="center"
android:text="@string/gps_track"
android:textSize="16sp" />
<ImageView
android:id="@+id/activitygpsview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:src="@tools:sample/avatars" />
</LinearLayout>
</RelativeLayout>

View File

@ -1022,6 +1022,7 @@
<string name="activity_detail_end_label">End</string>
<string name="activity_detail_duration_label">Duration</string>
<string name="activity_detail_show_gps_label">Show GPS Track</string>
<string name="gps_track">GPS track</string>
<!-- Device Actions Preferences -->
<string name="prefs_events_forwarding_summary">Use device events to trigger actions and Android broadcasts</string>
<string name="prefs_events_forwarding_title">Device actions</string>