1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-22 06:41:06 +02:00

Add Activity Summary Statistics Drawer

Move SummaryStatistics JSON Summary to own place
This commit is contained in:
vanous 2020-08-20 09:03:26 +02:00
parent ebc70b3fd5
commit b236a6af16
10 changed files with 478 additions and 105 deletions

View File

@ -17,7 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.app.DatePickerDialog; import android.app.DatePickerDialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
@ -31,12 +30,14 @@ import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView; import android.widget.AbsListView;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.DatePicker; import android.widget.DatePicker;
import android.widget.LinearLayout;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Spinner; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
@ -45,26 +46,33 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter; import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary; import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -76,6 +84,7 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
int activityFilter=0; int activityFilter=0;
long dateFromFilter=0; long dateFromFilter=0;
long dateToFilter=0; long dateToFilter=0;
boolean offscreen = true;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
@ -112,11 +121,20 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
boolean processed = false; boolean processed = false;
switch (item.getItemId()) { switch (item.getItemId()) {
case android.R.id.home:
// back button, close drawer if open, otherwise exit
if (!offscreen){
processSummaryStatistics();
}else{
finish();
}
return true;
case R.id.activity_action_manage_timestamp: case R.id.activity_action_manage_timestamp:
resetFetchTimestampToChosenDate(); resetFetchTimestampToChosenDate();
processed = true; processed = true;
break; break;
case R.id.activity_action_filter: case R.id.activity_action_filter:
if (!offscreen) processSummaryStatistics(); //hide drawer with stats if shown
Intent filterIntent = new Intent(this, ActivitySummariesFilter.class); Intent filterIntent = new Intent(this, ActivitySummariesFilter.class);
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putSerializable("activityKindMap",activityKindMap); bundle.putSerializable("activityKindMap",activityKindMap);
@ -126,10 +144,83 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
filterIntent.putExtras(bundle); filterIntent.putExtras(bundle);
startActivityForResult(filterIntent,1); startActivityForResult(filterIntent,1);
return true; return true;
case R.id.activity_action_calculate_summary_stats:
processSummaryStatistics();
return true;
} }
return processed; return processed;
} }
private void processSummaryStatistics() {
View hiddenLayout = findViewById(R.id.activity_summary_statistics_relative_layout);
hiddenLayout.setVisibility(View.VISIBLE);
//hide or show drawer
int yOffset = (offscreen) ? 0 : -1 * hiddenLayout.getHeight();
LinearLayout.LayoutParams rlParams =
(LinearLayout.LayoutParams) hiddenLayout.getLayoutParams();
rlParams.setMargins(0, yOffset, 0, 0);
hiddenLayout.setLayoutParams(rlParams);
Animation animFadeDown;
animFadeDown = AnimationUtils.loadAnimation(
this,
R.anim.slidefromtop);
setTitle(R.string.activity_summaries);
if (offscreen) {
setTitle(R.string.activity_summaries_statistics);
hiddenLayout.startAnimation(animFadeDown);
double durationSum = 0;
double caloriesBurntSum = 0;
double distanceSum = 0;
double activeSecondsSum = 0;
double firstItemDate = 0;
double lastItemDate = 0;
TextView durationSumView = findViewById(R.id.activity_stats_duration_sum_value);
TextView caloriesBurntSumView = findViewById(R.id.activity_stats_calories_burnt_sum_value);
TextView distanceSumView = findViewById(R.id.activity_stats_distance_sum_value);
TextView activeSecondsSumView = findViewById(R.id.activity_stats_activeSeconds_sum_value);
TextView timeStartView = findViewById(R.id.activity_stats_timeFrom_value);
TextView timeEndView = findViewById(R.id.activity_stats_timeTo_value);
for (BaseActivitySummary sportitem : getItemAdapter().getItems()) {
if (firstItemDate == 0) firstItemDate = sportitem.getStartTime().getTime();
lastItemDate = sportitem.getEndTime().getTime();
durationSum += sportitem.getEndTime().getTime() - sportitem.getStartTime().getTime();
ActivitySummaryJsonSummary activitySummaryJsonSummary = new ActivitySummaryJsonSummary(sportitem);
JSONObject summarySubdata = activitySummaryJsonSummary.getSummaryData();
if (summarySubdata != null) {
try {
if (summarySubdata.has("caloriesBurnt")) {
caloriesBurntSum += summarySubdata.getJSONObject("caloriesBurnt").getDouble("value");
}
if (summarySubdata.has("distanceMeters")) {
distanceSum += summarySubdata.getJSONObject("distanceMeters").getDouble("value");
}
if (summarySubdata.has("activeSeconds")) {
activeSecondsSum += summarySubdata.getJSONObject("activeSeconds").getDouble("value");
}
} catch (JSONException e) {
LOG.error("SportsActivity", e);
}
}
}
DecimalFormat df = new DecimalFormat("#.##");
durationSumView.setText(String.format("%s", DateTimeUtils.formatDurationHoursMinutes((long) durationSum, TimeUnit.MILLISECONDS)));
caloriesBurntSumView.setText(String.format("%s %s", (long) caloriesBurntSum, getString(R.string.calories_unit)));
distanceSumView.setText(String.format("%s %s", df.format(distanceSum / 1000), getString(R.string.km)));
activeSecondsSumView.setText(String.format("%s", DateTimeUtils.formatDurationHoursMinutes((long) activeSecondsSum, TimeUnit.SECONDS)));
//start and end are inverted when filer not applied, because items are sorted the other way
timeStartView.setText((dateFromFilter != 0) ? DateTimeUtils.formatDate(new Date(dateFromFilter)) : DateTimeUtils.formatDate(new Date((long) lastItemDate)));
timeEndView.setText((dateToFilter != 0) ? DateTimeUtils.formatDate(new Date(dateToFilter)) : DateTimeUtils.formatDate(new Date((long) firstItemDate)));
}
offscreen = !offscreen;
}
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) { public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData); super.onActivityResult(requestCode, resultCode, resultData);
@ -177,6 +268,9 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
}); });
getItemListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); getItemListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
//hide top drawer on init
View hiddenLayout = findViewById(R.id.activity_summary_statistics_relative_layout);
hiddenLayout.setVisibility(View.GONE);
getItemListView().setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { getItemListView().setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
@Override @Override

View File

@ -37,8 +37,6 @@ import android.widget.TableRow;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -52,12 +50,11 @@ import java.util.Iterator;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary; import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -65,7 +62,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents;
public class ActivitySummaryDetail extends AbstractGBActivity { public class ActivitySummaryDetail extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class); private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class);
private JSONObject groupData = setGroups();
private boolean show_raw_data = false; private boolean show_raw_data = false;
BaseActivitySummary currentItem = null; BaseActivitySummary currentItem = null;
private int alternateColor; private int alternateColor;
@ -197,28 +195,10 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
TableLayout fieldLayout = findViewById(R.id.summaryDetails); TableLayout fieldLayout = findViewById(R.id.summaryDetails);
fieldLayout.removeAllViews(); //remove old widgets fieldLayout.removeAllViews(); //remove old widgets
ActivitySummaryJsonSummary activitySummaryJsonSummary = new ActivitySummaryJsonSummary(item);
JSONObject summarySubdata = null; //JSONObject summarySubdata = activitySummaryJsonSummary.getSummaryData();
JSONObject data; JSONObject data = activitySummaryJsonSummary.getSummaryGroupedList(); //get list, grouped by groups
if (data == null) return;
// Parse on the fly if we have binary data available
if (item.getRawSummaryData() != null) {
ActivitySummaryParser parser = new HuamiActivitySummaryParser(); // FIXME: if something else than huami supports that make sure to have the right parser
item = parser.parseBinaryData(item);
}
String sumData = item.getSummaryData();
if (sumData != null) {
try {
summarySubdata = new JSONObject(sumData);
} catch (JSONException e) {
LOG.error("SportsActivity", e);
}
}
if (summarySubdata == null) return;
data = makeSummaryList(summarySubdata); //make new list, grouped by groups
Iterator<String> keys = data.keys(); Iterator<String> keys = data.keys();
DecimalFormat df = new DecimalFormat("#.##"); DecimalFormat df = new DecimalFormat("#.##");
@ -294,76 +274,9 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
} }
} }
private JSONObject setGroups(){
String groupDefinitions = "{'Strokes':['averageStrokeDistance','averageStrokesPerSecond','strokes'], " +
"'Swimming':['swolfIndex','swimStyle'], " +
"'Elevation':['ascentMeters','descentMeters','maxAltitude','minAltitude','ascentSeconds','descentSeconds','flatSeconds'], " +
"'Speed':['maxSpeed','minPace','maxPace','averageKMPaceSeconds'], " +
"'Activity':['distanceMeters','steps','activeSeconds','caloriesBurnt','totalStride'," +
"'averageHR','averageStride'], " +
"'Laps':['averageLapPace','laps']}";
JSONObject data = null;
try {
data = new JSONObject(groupDefinitions);
} catch (JSONException e) {
LOG.error("SportsActivity", e);
}
return data;
}
private String getGroup(String searchItem) {
String defaultGroup = "Activity";
if (groupData == null) return defaultGroup;
Iterator<String> keys = groupData.keys();
while (keys.hasNext()) {
String key = keys.next();
try {
JSONArray itemList = (JSONArray) groupData.get(key);
for (int i = 0; i < itemList.length(); i++) {
if (itemList.getString(i).equals(searchItem)) {
return key;
}
}
} catch (JSONException e) {
LOG.error("SportsActivity", e);
}
}
return defaultGroup;
}
private JSONObject makeSummaryList(JSONObject summaryData){
//make dictionary with data for each group
JSONObject list = new JSONObject();
Iterator<String> keys = summaryData.keys();
LOG.error("SportsActivity JSON:" + summaryData + keys);
while (keys.hasNext()) {
String key = keys.next();
try {
LOG.error("SportsActivity:" + key + ": " + summaryData.get(key) + "\n");
JSONObject innerData = (JSONObject) summaryData.get(key);
Object value = innerData.get("value");
String unit = innerData.getString("unit");
String group = getGroup(key);
if (!list.has(group)) {
list.put(group,new JSONArray());
}
JSONArray tmpl = (JSONArray) list.get(group);
JSONObject innernew = new JSONObject();
innernew.put("name", key);
innernew.put("value", value);
innernew.put("unit", unit);
tmpl.put(innernew);
list.put(group, tmpl);
} catch (JSONException e) {
LOG.error("SportsActivity", e);
}
}
return list;
}
public static int getAlternateColor(Context context) { public static int getAlternateColor(Context context) {
TypedValue typedValue = new TypedValue(); TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme(); Resources.Theme theme = context.getTheme();

View File

@ -21,6 +21,9 @@ import org.json.JSONObject;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
/** /**
* Summarized information about a temporal activity. * Summarized information about a temporal activity.
* *

View File

@ -0,0 +1,131 @@
package nodomain.freeyourgadget.gadgetbridge.model;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
public class ActivitySummaryJsonSummary {
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryJsonSummary.class);
private JSONObject groupData;
private JSONObject summaryData;
private JSONObject summaryGroupedList;
private BaseActivitySummary baseActivitySummary;
public ActivitySummaryJsonSummary(BaseActivitySummary baseActivitySummary){
this.baseActivitySummary=baseActivitySummary;
}
private JSONObject setSummaryData(BaseActivitySummary item){
String summary = getCorrectSummary(item);
JSONObject jsonSummary = getJSONSummary(summary);
return jsonSummary;
}
public JSONObject getSummaryData(){
//returns json with summaryData
if (summaryData==null) summaryData=setSummaryData(baseActivitySummary);
return summaryData;
}
private String getCorrectSummary(BaseActivitySummary item){
if (item.getRawSummaryData() != null) {
ActivitySummaryParser parser = new HuamiActivitySummaryParser(); // FIXME: if something else than huami supports that make sure to have the right parser
item = parser.parseBinaryData(item);
}
return item.getSummaryData();
}
private JSONObject getJSONSummary(String sumData){
JSONObject summarySubdata = null;
if (sumData != null) {
try {
summarySubdata = new JSONObject(sumData);
} catch (JSONException e) {
}
}
return summarySubdata;
}
public JSONObject getSummaryGroupedList() {
//returns list grouped by activity groups as per createActivitySummaryGroups
if (summaryData==null) summaryData=setSummaryData(baseActivitySummary);
if (summaryGroupedList==null) summaryGroupedList=setSummaryGroupedList(summaryData);
return summaryGroupedList;
}
private JSONObject setSummaryGroupedList(JSONObject summaryDatalist){
this.groupData = createActivitySummaryGroups(); //structure for grouping activities into groups, when vizualizing
if (summaryDatalist ==null ) return null;
Iterator<String> keys = summaryDatalist.keys();
JSONObject list=new JSONObject();
while (keys.hasNext()) {
String key = keys.next();
try {
JSONObject innerData = (JSONObject) summaryDatalist.get(key);
Object value = innerData.get("value");
String unit = innerData.getString("unit");
String group = getGroup(key);
if (!list.has(group)) {
list.put(group,new JSONArray());
}
JSONArray tmpl = (JSONArray) list.get(group);
JSONObject innernew = new JSONObject();
innernew.put("name", key);
innernew.put("value", value);
innernew.put("unit", unit);
tmpl.put(innernew);
list.put(group, tmpl);
} catch (JSONException e) {
LOG.error("SportsActivity", e);
}
}
return list;
}
private String getGroup(String searchItem) {
String defaultGroup = "Activity";
if (groupData == null) return defaultGroup;
Iterator<String> keys = groupData.keys();
while (keys.hasNext()) {
String key = keys.next();
try {
JSONArray itemList = (JSONArray) groupData.get(key);
for (int i = 0; i < itemList.length(); i++) {
if (itemList.getString(i).equals(searchItem)) {
return key;
}
}
} catch (JSONException e) {
LOG.error("SportsActivity", e);
}
}
return defaultGroup;
}
private JSONObject createActivitySummaryGroups(){
String groupDefinitions = "{'Strokes':['averageStrokeDistance','averageStrokesPerSecond','strokes'], " +
"'Swimming':['swolfIndex','swimStyle'], " +
"'Elevation':['ascentMeters','descentMeters','maxAltitude','minAltitude','ascentSeconds','descentSeconds','flatSeconds'], " +
"'Speed':['maxSpeed','minPace','maxPace','averageKMPaceSeconds'], " +
"'Activity':['distanceMeters','steps','activeSeconds','caloriesBurnt','totalStride'," +
"'averageHR','averageStride'], " +
"'Laps':['averageLapPace','laps']}";
JSONObject data = null;
try {
data = new JSONObject(groupDefinitions);
} catch (JSONException e) {
LOG.error("SportsActivity", e);
}
return data;
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:interpolator="@android:anim/decelerate_interpolator">
<translate
android:fromXDelta="0%" android:toXDelta="0%"
android:fromYDelta="-200%" android:toYDelta="0%"
android:duration="100" />
</set>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM14,12L14,8c0,-1.11 -0.9,-2 -2,-2h-1c-1.1,0 -2,0.89 -2,2v1c0,1.11 0.9,2 2,2h1v1L9,12v2h3c1.1,0 2,-0.89 2,-2zM11,9L11,8h1v1h-1zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,9h-2L19,7h-2v2h-2v2h2v2h2v-2h2v6L7,17L7,3h14v6z"/>
</vector>

View File

@ -17,6 +17,7 @@
</LinearLayout> </LinearLayout>
<include layout="@layout/activity_summary_statistics"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/list_activity_swipe_layout" android:id="@+id/list_activity_swipe_layout"

View File

@ -0,0 +1,202 @@
<?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:id="@+id/activity_summary_statistics_relative_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:clickable="false"
android:focusable="auto"
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/activity_stats_timeFrom_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="@string/activity_filter_date_from"
android:textAlignment="textStart"
android:textSize="18sp" />
<TextView
android:id="@+id/activity_stats_timeFrom_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:textAlignment="textEnd"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/activity_stats_timeTo_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="@string/activity_filter_date_to"
android:textAlignment="textStart"
android:textSize="18sp" />
<TextView
android:id="@+id/activity_stats_timeTo_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:textAlignment="textEnd"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/activity_stats_duration_sum_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="@string/activity_detail_duration_label"
android:textAlignment="textStart"
android:textSize="18sp" />
<TextView
android:id="@+id/activity_stats_duration_sum_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:textAlignment="textEnd"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/activity_stats_distance_sum_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="@string/Distance"
android:textAlignment="textStart"
android:textSize="18sp" />
<TextView
android:id="@+id/activity_stats_distance_sum_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:textAlignment="textEnd"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/activity_stats_activeSeconds_sum_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="@string/activeSeconds"
android:textAlignment="textStart"
android:textSize="18sp" />
<TextView
android:id="@+id/activity_stats_activeSeconds_sum_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:textAlignment="textEnd"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/activity_stats_calories_burnt_sum_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="@string/caloriesBurnt"
android:textAlignment="textStart"
android:textSize="18sp" />
<TextView
android:id="@+id/activity_stats_calories_burnt_sum_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:fontFamily="sans-serif-black"
android:maxLines="1"
android:scrollHorizontally="false"
android:textAlignment="textEnd"
android:textSize="18sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray" />
</LinearLayout>
</RelativeLayout>

View File

@ -13,5 +13,11 @@
android:title="@string/pref_header_filter" android:title="@string/pref_header_filter"
app:iconTint="@color/primarytext_dark" app:iconTint="@color/primarytext_dark"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/activity_action_calculate_summary_stats"
android:icon="@drawable/ic_outline_filter_9_plus"
android:title="@string/pref_header_filter"
app:iconTint="@color/primarytext_dark"
app:showAsAction="ifRoom" />
</menu> </menu>

View File

@ -952,6 +952,7 @@
<string name="seconds_m">sec/m</string> <string name="seconds_m">sec/m</string>
<string name="minutes_km">min/km</string> <string name="minutes_km">min/km</string>
<string name="bpm">bpm</string> <string name="bpm">bpm</string>
<string name="km">km</string>
<!-- activity summary groups--> <!-- activity summary groups-->
<string name="Strokes">Strokes</string> <string name="Strokes">Strokes</string>
<string name="Swimming">Swimming</string> <string name="Swimming">Swimming</string>
@ -961,9 +962,9 @@
<string name="Activity">Activity</string> <string name="Activity">Activity</string>
<string name="Steps">Steps</string> <string name="Steps">Steps</string>
<!-- Sports Activity Detail --> <!-- Sports Activity Detail -->
<string name="activity_detail_start_label">Start:</string> <string name="activity_detail_start_label">Start</string>
<string name="activity_detail_end_label">End:</string> <string name="activity_detail_end_label">End</string>
<string name="activity_detail_duration_label">Duration:</string> <string name="activity_detail_duration_label">Duration</string>
<string name="activity_detail_show_gps_label">Show GPS Track</string> <string name="activity_detail_show_gps_label">Show GPS Track</string>
<!-- Device Actions Preferences --> <!-- Device Actions Preferences -->
<string name="prefs_events_forwarding_summary">Use device events to trigger actions and Android broadcasts</string> <string name="prefs_events_forwarding_summary">Use device events to trigger actions and Android broadcasts</string>
@ -977,11 +978,12 @@
<string name="prefs_events_forwarding_broadcast_title">Broadcast message</string> <string name="prefs_events_forwarding_broadcast_title">Broadcast message</string>
<string name="prefs_events_forwarding_action_title">Run action</string> <string name="prefs_events_forwarding_action_title">Run action</string>
<string name="pref_header_filter">Sports Activities Filter</string> <string name="pref_header_filter">Sports Activities Filter</string>
<string name="activity_filter_date_from">From:</string> <string name="activity_filter_date_from">From</string>
<string name="activity_filter_date_to">To:</string> <string name="activity_filter_date_to">To</string>
<string name="activity_filter_reset_filter">Reset Filter</string> <string name="activity_filter_reset_filter">Reset Filter</string>
<string name="activity_filter_filter_title">Filter</string> <string name="activity_filter_filter_title">Filter</string>
<string name="activity_filter_apply_filter">Apply Filter</string> <string name="activity_filter_apply_filter">Apply Filter</string>
<string name="activity_summaries_statistics">Statistics</string>
<plurals name="widget_alarm_target_hours"> <plurals name="widget_alarm_target_hours">