mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-30 14:02:56 +01:00
Refactor activity lists to use a RecyclerView
This commit is contained in:
parent
32e955abe2
commit
22a6d9b7d9
@ -17,7 +17,9 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -26,7 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractActivityListingAdapt
|
||||
|
||||
public abstract class AbstractListActivity<T> extends AbstractGBActivity {
|
||||
private AbstractActivityListingAdapter<T> itemAdapter;
|
||||
private ListView itemListView;
|
||||
private RecyclerView itemListView;
|
||||
|
||||
public void setItemAdapter(AbstractActivityListingAdapter<T> itemAdapter) {
|
||||
this.itemAdapter = itemAdapter;
|
||||
@ -37,23 +39,23 @@ public abstract class AbstractListActivity<T> extends AbstractGBActivity {
|
||||
this.itemAdapter.loadItems();
|
||||
}
|
||||
|
||||
public void setActivityKindFilter(int activityKind){
|
||||
public void setActivityKindFilter(int activityKind) {
|
||||
this.itemAdapter.setActivityKindFilter(activityKind);
|
||||
}
|
||||
|
||||
public void setDateFromFilter(long date){
|
||||
public void setDateFromFilter(long date) {
|
||||
this.itemAdapter.setDateFromFilter(date);
|
||||
}
|
||||
|
||||
public void setDateToFilter(long date){
|
||||
public void setDateToFilter(long date) {
|
||||
this.itemAdapter.setDateToFilter(date);
|
||||
}
|
||||
|
||||
public void setNameContainsFilter(String name){
|
||||
public void setNameContainsFilter(String name) {
|
||||
this.itemAdapter.setNameContainsFilter(name);
|
||||
}
|
||||
|
||||
public void setItemsFilter(List items) {
|
||||
public void setItemsFilter(List<Long> items) {
|
||||
this.itemAdapter.setItemsFilter(items);
|
||||
}
|
||||
|
||||
@ -65,15 +67,12 @@ public abstract class AbstractListActivity<T> extends AbstractGBActivity {
|
||||
return itemAdapter;
|
||||
}
|
||||
|
||||
public ListView getItemListView() {
|
||||
return itemListView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_list);
|
||||
itemListView = findViewById(R.id.itemListView);
|
||||
itemListView.setLayoutManager(new LinearLayoutManager(this));
|
||||
}
|
||||
}
|
||||
|
@ -34,13 +34,13 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.DatePicker;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
@ -49,6 +49,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -66,6 +67,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ActivitySummaryUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
@ -80,7 +82,10 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
List<Long> itemsFilter;
|
||||
String nameContainsFilter;
|
||||
private GBDevice mGBDevice;
|
||||
private BitSet selectedItems;
|
||||
private SwipeRefreshLayout swipeLayout;
|
||||
private ActionMode mActionMode;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -160,7 +165,6 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
if (requestCode == ACTIVITY_DETAIL) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -177,62 +181,96 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
ActivitySummariesAdapter activitySummariesAdapter = new ActivitySummariesAdapter(this, mGBDevice, activityFilter, dateFromFilter, dateToFilter, nameContainsFilter, deviceFilter, itemsFilter);
|
||||
int backgroundColor = getBackgroundColor(ActivitySummariesActivity.this);
|
||||
activitySummariesAdapter.setBackgroundColor(backgroundColor);
|
||||
activitySummariesAdapter.setShowTime(false);
|
||||
setItemAdapter(activitySummariesAdapter);
|
||||
|
||||
getItemListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
ActivitySummariesAdapter activitySummariesAdapter = new ActivitySummariesAdapter(this, mGBDevice, activityFilter, dateFromFilter, dateToFilter, nameContainsFilter, deviceFilter, itemsFilter);
|
||||
selectedItems = activitySummariesAdapter.getSelectedItems();
|
||||
|
||||
activitySummariesAdapter.setOnItemClickListener(position -> {
|
||||
if (!selectedItems.isEmpty()) {
|
||||
selectedItems.set(position, !selectedItems.get(position));
|
||||
activitySummariesAdapter.notifyItemChanged(position);
|
||||
if (!selectedItems.isEmpty()) {
|
||||
startActionMode();
|
||||
} else {
|
||||
stopActionMode();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (position == 0) return; // item 0 is empty for dashboard
|
||||
Object item = parent.getItemAtPosition(position);
|
||||
if (item != null) {
|
||||
ActivitySummary summary = (ActivitySummary) item;
|
||||
ActivitySummary summary = activitySummariesAdapter.getItem(position);
|
||||
if (summary != null) {
|
||||
try {
|
||||
showActivityDetail(position);
|
||||
} catch (Exception e) {
|
||||
GB.toast(getApplicationContext(), "Unable to display Activity Detail, maybe the activity is not available yet: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
activitySummariesAdapter.setOnItemLongClickListener(position -> {
|
||||
selectedItems.set(position, !selectedItems.get(position));
|
||||
activitySummariesAdapter.notifyItemChanged(position);
|
||||
|
||||
getItemListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
|
||||
if (!selectedItems.isEmpty()) {
|
||||
startActionMode();
|
||||
} else {
|
||||
stopActionMode();
|
||||
}
|
||||
});
|
||||
setItemAdapter(activitySummariesAdapter);
|
||||
|
||||
getItemListView().setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(ActionMode actionMode, int position, long id, boolean checked) {
|
||||
if (position == 0 && checked) subtrackDashboard = 1;
|
||||
if (position == 0 && !checked) subtrackDashboard = 0;
|
||||
final int selectedItems = getItemListView().getCheckedItemCount() - subtrackDashboard;
|
||||
actionMode.setTitle(selectedItems + " selected");
|
||||
swipeLayout = findViewById(R.id.list_activity_swipe_layout);
|
||||
swipeLayout.setOnRefreshListener(this::fetchTrackData);
|
||||
|
||||
FloatingActionButton fab = findViewById(R.id.fab);
|
||||
fab.setOnClickListener(v -> fetchTrackData());
|
||||
|
||||
activityKindMap = fillKindMap();
|
||||
}
|
||||
|
||||
private void stopActionMode() {
|
||||
if (mActionMode != null) {
|
||||
mActionMode.finish();
|
||||
mActionMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void startActionMode() {
|
||||
int[] numSelected = new int[]{0};
|
||||
for (int i = 0; i < selectedItems.length(); i++) {
|
||||
if (selectedItems.get(i)) {
|
||||
numSelected[0]++;
|
||||
}
|
||||
}
|
||||
if (mActionMode != null) {
|
||||
// already in action mode
|
||||
mActionMode.setTitle(getString(R.string.number_selected_items, numSelected[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
mActionMode = startActionMode(new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
|
||||
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
|
||||
mode.setTitle(getString(R.string.number_selected_items, numSelected[0]));
|
||||
|
||||
getMenuInflater().inflate(R.menu.activity_list_context_menu, menu);
|
||||
findViewById(R.id.fab).setVisibility(View.INVISIBLE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
|
||||
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
|
||||
boolean processed = false;
|
||||
final SparseBooleanArray checked = getItemListView().getCheckedItemPositions();
|
||||
final int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.activity_action_delete) {
|
||||
final List<BaseActivitySummary> toDelete = new ArrayList<>();
|
||||
for (int i = 0; i < checked.size(); i++) {
|
||||
if (checked.valueAt(i)) {
|
||||
toDelete.add(getItemAdapter().getItem(checked.keyAt(i)));
|
||||
for (int i = 0; i < selectedItems.length(); i++) {
|
||||
if (selectedItems.get(i)) {
|
||||
toDelete.add(getItemAdapter().getItem(i));
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,16 +290,13 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
} else if (itemId == R.id.activity_action_export) {
|
||||
final List<String> paths = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < checked.size(); i++) {
|
||||
if (checked.valueAt(i)) {
|
||||
|
||||
BaseActivitySummary item = getItemAdapter().getItem(checked.keyAt(i));
|
||||
if (item != null) {
|
||||
ActivitySummary summary = item;
|
||||
|
||||
String gpxTrack = summary.getGpxTrack();
|
||||
for (int i = 0; i < selectedItems.length(); i++) {
|
||||
if (selectedItems.get(i)) {
|
||||
BaseActivitySummary summary = getItemAdapter().getItem(i);
|
||||
if (summary != null) {
|
||||
File gpxTrack = ActivitySummaryUtils.getGpxFile(summary);
|
||||
if (gpxTrack != null) {
|
||||
paths.add(gpxTrack);
|
||||
paths.add(gpxTrack.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -269,17 +304,21 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
shareMultiple(paths);
|
||||
processed = true;
|
||||
} else if (itemId == R.id.activity_action_select_all) {
|
||||
for (int i = 0; i < getItemListView().getCount(); i++) {
|
||||
getItemListView().setItemChecked(i, true);
|
||||
for (int i = 1; i < getItemAdapter().getItemCount() - 1; i++) {
|
||||
if (!selectedItems.get(i)) {
|
||||
numSelected[0]++;
|
||||
selectedItems.set(i, true);
|
||||
getItemAdapter().notifyItemChanged(i);
|
||||
}
|
||||
return true; //don't finish actionmode in this case!
|
||||
}
|
||||
actionMode.setTitle(getString(R.string.number_selected_items, numSelected[0]));
|
||||
return true; //don't finish actionMode in this case!
|
||||
} else if (itemId == R.id.activity_action_addto_filter) {
|
||||
final List<Long> toFilter = new ArrayList<>();
|
||||
for (int i = 0; i < checked.size(); i++) {
|
||||
if (checked.valueAt(i)) {
|
||||
BaseActivitySummary item = getItemAdapter().getItem(checked.keyAt(i));
|
||||
if (item != null && item.getId() != null) {
|
||||
ActivitySummary summary = item;
|
||||
for (int i = 0; i < selectedItems.length(); i++) {
|
||||
if (selectedItems.get(i)) {
|
||||
BaseActivitySummary summary = getItemAdapter().getItem(i);
|
||||
if (summary != null && summary.getId() != null) {
|
||||
Long id = summary.getId();
|
||||
toFilter.add(id);
|
||||
}
|
||||
@ -292,34 +331,22 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
processed = true;
|
||||
}
|
||||
actionMode.finish();
|
||||
mActionMode = null;
|
||||
return processed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode actionMode) {
|
||||
public void onDestroyActionMode(final ActionMode mode) {
|
||||
mActionMode = null;
|
||||
for (int i = 0; i < selectedItems.length(); i++) {
|
||||
if (selectedItems.get(i)) {
|
||||
selectedItems.set(i, false);
|
||||
getItemAdapter().notifyItemChanged(i);
|
||||
}
|
||||
}
|
||||
findViewById(R.id.fab).setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
swipeLayout = findViewById(R.id.list_activity_swipe_layout);
|
||||
swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
fetchTrackData();
|
||||
}
|
||||
});
|
||||
|
||||
FloatingActionButton fab = findViewById(R.id.fab);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
fetchTrackData();
|
||||
}
|
||||
});
|
||||
|
||||
activityKindMap = fillKindMap();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, ActivityKind> fillKindMap() {
|
||||
@ -362,11 +389,11 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
for (BaseActivitySummary item : items) {
|
||||
try {
|
||||
item.delete();
|
||||
getItemAdapter().remove(item);
|
||||
} catch (Exception e) {
|
||||
//pass delete error
|
||||
}
|
||||
}
|
||||
// Adapter is fully reloaded after refresh
|
||||
refresh();
|
||||
}
|
||||
|
||||
@ -398,7 +425,6 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
} else {
|
||||
GB.toast(this, "No selected activity contains a GPX track to share", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void showActivityDetail(int position) {
|
||||
|
@ -75,7 +75,6 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -85,18 +84,13 @@ import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.Activity
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ActivitySummaryUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
@ -578,71 +572,33 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
}
|
||||
|
||||
private void viewGpxTrack(Context context) {
|
||||
final File trackFile = getTrackFile();
|
||||
if (trackFile == null) {
|
||||
final File gpxFile = ActivitySummaryUtils.getGpxFile(currentItem);
|
||||
if (gpxFile == null) {
|
||||
GB.toast(getApplicationContext(), "No GPX track in this activity", Toast.LENGTH_LONG, GB.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (trackFile.getName().endsWith(".gpx")) {
|
||||
AndroidUtils.viewFile(trackFile.getPath(), "application/gpx+xml", context);
|
||||
} else if (trackFile.getName().endsWith(".fit")) {
|
||||
final File gpxFile = convertFitToGpx(trackFile);
|
||||
AndroidUtils.viewFile(gpxFile.getPath(), "application/gpx+xml", context);
|
||||
} else {
|
||||
GB.toast(getApplicationContext(), "Unknown track format", Toast.LENGTH_LONG, GB.INFO);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
GB.toast(getApplicationContext(), "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void shareGpxTrack(final Context context) {
|
||||
final File trackFile = getTrackFile();
|
||||
if (trackFile == null) {
|
||||
final File gpxFile = ActivitySummaryUtils.getGpxFile(currentItem);
|
||||
if (gpxFile == null) {
|
||||
GB.toast(getApplicationContext(), "No GPX track in this activity", Toast.LENGTH_LONG, GB.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (trackFile.getName().endsWith(".gpx")) {
|
||||
AndroidUtils.shareFile(context, trackFile);
|
||||
} else if (trackFile.getName().endsWith(".fit")) {
|
||||
final File gpxFile = convertFitToGpx(trackFile);
|
||||
AndroidUtils.shareFile(context, gpxFile);
|
||||
} else {
|
||||
GB.toast(getApplicationContext(), "Unknown track format", Toast.LENGTH_LONG, GB.INFO);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Unable to share GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private File convertFitToGpx(final File file) throws IOException, ActivityTrackExporter.GPXTrackEmptyException {
|
||||
final FitFile fitFile = FitFile.parseIncoming(file);
|
||||
final List<ActivityPoint> activityPoints = fitFile.getRecords().stream()
|
||||
.filter(r -> r instanceof FitRecord)
|
||||
.map(r -> ((FitRecord) r).toActivityPoint())
|
||||
.filter(ap -> ap.getLocation() != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final ActivityTrack activityTrack = new ActivityTrack();
|
||||
activityTrack.setName(currentItem.getName());
|
||||
activityTrack.addTrackPoints(activityPoints);
|
||||
|
||||
final File cacheDir = getCacheDir();
|
||||
final File rawCacheDir = new File(cacheDir, "gpx");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
rawCacheDir.mkdir();
|
||||
final File gpxFile = new File(rawCacheDir, file.getName().replace(".fit", ".gpx"));
|
||||
|
||||
final GPXExporter gpxExporter = new GPXExporter();
|
||||
gpxExporter.performExport(activityTrack, gpxFile);
|
||||
|
||||
return gpxFile;
|
||||
}
|
||||
|
||||
private static void shareRawSummary(final Context context, final BaseActivitySummary summary) {
|
||||
if (summary.getRawSummaryData() == null) {
|
||||
GB.toast(context, "No raw summary in this activity", Toast.LENGTH_LONG, GB.WARN);
|
||||
@ -732,15 +688,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
|
||||
@Nullable
|
||||
private File getTrackFile() {
|
||||
final String gpxTrack = currentItem.getGpxTrack();
|
||||
if (gpxTrack != null) {
|
||||
return FileUtils.tryFixPath(new File(gpxTrack));
|
||||
}
|
||||
final String rawDetails = currentItem.getRawDetailsPath();
|
||||
if (rawDetails != null && rawDetails.endsWith(".fit")) {
|
||||
return FileUtils.tryFixPath(new File(rawDetails));
|
||||
}
|
||||
return null;
|
||||
return ActivitySummaryUtils.getTrackFile(currentItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -24,6 +24,9 @@ import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.github.mikephil.charting.animation.Easing;
|
||||
import com.github.mikephil.charting.charts.PieChart;
|
||||
import com.github.mikephil.charting.data.PieData;
|
||||
@ -36,14 +39,11 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractActivityListingAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityListItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
@ -54,8 +54,6 @@ public class ActivityListingAdapter extends AbstractActivityListingAdapter<Activ
|
||||
public static final String CHART_COLOR_END = "#2ecc71";
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingAdapter.class);
|
||||
protected final int ANIM_TIME = 250;
|
||||
private final int SESSION_SUMMARY = ActivitySession.SESSION_SUMMARY;
|
||||
private final int SESSION_EMPTY = ActivitySession.SESSION_EMPTY;
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
int stepsGoal = activityUser.getStepsGoal();
|
||||
int distanceGoalMeters = activityUser.getDistanceGoalMeters();
|
||||
@ -65,109 +63,27 @@ public class ActivityListingAdapter extends AbstractActivityListingAdapter<Activ
|
||||
super(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View fill_dashboard(ActivitySession item, int position, View view, ViewGroup parent, Context context) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.activity_list_dashboard_item, parent, false);
|
||||
PieChart ActiveStepsChart;
|
||||
PieChart DistanceChart;
|
||||
PieChart ActiveTimeChart;
|
||||
|
||||
ActiveStepsChart = view.findViewById(R.id.activity_dashboard_piechart1);
|
||||
setUpChart(ActiveStepsChart);
|
||||
int steps = item.getActiveSteps();
|
||||
setChartsData(ActiveStepsChart, steps, stepsGoal, context.getString(R.string.activity_list_summary_active_steps), context);
|
||||
|
||||
DistanceChart = view.findViewById(R.id.activity_dashboard_piechart2);
|
||||
setUpChart(DistanceChart);
|
||||
float distance = item.getDistance();
|
||||
setChartsData(DistanceChart, distance, distanceGoalMeters, context.getString(R.string.distance), context);
|
||||
|
||||
ActiveTimeChart = view.findViewById(R.id.activity_dashboard_piechart3);
|
||||
setUpChart(ActiveTimeChart);
|
||||
long duration = item.getEndTime().getTime() - item.getStartTime().getTime();
|
||||
setChartsData(ActiveTimeChart, duration, activeTimeGoalTimeMillis, context.getString(R.string.activity_list_summary_active_time), context);
|
||||
|
||||
TextView stepLabel = view.findViewById(R.id.line_layout_step_label);
|
||||
TextView stepTotalLabel = view.findViewById(R.id.line_layout_total_step_label);
|
||||
TextView distanceLabel = view.findViewById(R.id.line_layout_distance_label);
|
||||
TextView hrLabel = view.findViewById(R.id.heartrate_widget_label);
|
||||
TextView intensityLabel = view.findViewById(R.id.intensity_widget_label);
|
||||
TextView intensity2Label = view.findViewById(R.id.line_layout_intensity2_label);
|
||||
TextView durationLabel = view.findViewById(R.id.line_layout_duration_label);
|
||||
TextView sessionCountLabel = view.findViewById(R.id.line_layout_count_label);
|
||||
LinearLayout durationLayout = view.findViewById(R.id.line_layout_duration);
|
||||
LinearLayout countLayout = view.findViewById(R.id.line_layout_count);
|
||||
View hrLayout = view.findViewById(R.id.heartrate_widget_icon);
|
||||
LinearLayout stepsLayout = view.findViewById(R.id.line_layout_step);
|
||||
LinearLayout stepsTotalLayout = view.findViewById(R.id.line_layout_total_step);
|
||||
LinearLayout distanceLayout = view.findViewById(R.id.line_layout_distance);
|
||||
View intensityLayout = view.findViewById(R.id.intensity_widget_icon);
|
||||
View intensity2Layout = view.findViewById(R.id.line_layout_intensity2);
|
||||
|
||||
stepLabel.setText(getStepLabel(item));
|
||||
stepTotalLabel.setText(getStepTotalLabel(item));
|
||||
distanceLabel.setText(getDistanceLabel(item));
|
||||
hrLabel.setText(getHrLabel(item));
|
||||
intensityLabel.setText(getIntensityLabel(item));
|
||||
intensity2Label.setText(getIntensityLabel(item));
|
||||
durationLabel.setText(getDurationLabel(item));
|
||||
sessionCountLabel.setText(getSessionCountLabel(item));
|
||||
|
||||
if (!hasHR(item)) {
|
||||
hrLayout.setVisibility(View.GONE);
|
||||
hrLabel.setVisibility(View.GONE);
|
||||
} else {
|
||||
hrLayout.setVisibility(View.VISIBLE);
|
||||
hrLabel.setVisibility(View.VISIBLE);
|
||||
public AbstractActivityListingViewHolder<ActivitySession> onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||
switch (viewType) {
|
||||
case 0: // dashboard
|
||||
return new DashboardViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_dashboard_item, parent, false));
|
||||
case 2: // item
|
||||
return new ActivityItemViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_item, parent, false));
|
||||
}
|
||||
|
||||
if (!hasIntensity(item)) {
|
||||
intensityLayout.setVisibility(View.GONE);
|
||||
intensity2Layout.setVisibility(View.GONE);
|
||||
intensityLabel.setVisibility(View.GONE);
|
||||
intensity2Label.setVisibility(View.GONE);
|
||||
} else {
|
||||
intensityLayout.setVisibility(View.VISIBLE);
|
||||
intensity2Layout.setVisibility(View.VISIBLE);
|
||||
intensityLabel.setVisibility(View.VISIBLE);
|
||||
intensity2Label.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (!hasDistance(item)) {
|
||||
distanceLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
distanceLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (!hasSteps(item)) {
|
||||
stepsLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
stepsLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (!hasTotalSteps(item)) {
|
||||
stepsTotalLayout.setVisibility(View.GONE);
|
||||
countLayout.setVisibility(View.GONE);
|
||||
durationLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
stepsTotalLayout.setVisibility(View.VISIBLE);
|
||||
countLayout.setVisibility(View.VISIBLE);
|
||||
durationLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
return view;
|
||||
return super.onCreateViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
private void setChartsData(PieChart pieChart, float value, float target, String label, Context context) {
|
||||
ArrayList<PieEntry> entries = new ArrayList<>();
|
||||
entries.add(new PieEntry((float) value, context.getResources().getDrawable(R.drawable.ic_star_gold)));
|
||||
entries.add(new PieEntry(value, AppCompatResources.getDrawable(context, R.drawable.ic_star_gold)));
|
||||
|
||||
Easing.EasingFunction animationEffect = Easing.EaseInOutSine;
|
||||
|
||||
if (value < target) {
|
||||
entries.add(new PieEntry((float) (target - value)));
|
||||
entries.add(new PieEntry(target - value));
|
||||
}
|
||||
|
||||
pieChart.setCenterText(String.format("%d%%\n%s", (int) (value * 100 / target), label));
|
||||
@ -194,22 +110,6 @@ public class ActivityListingAdapter extends AbstractActivityListingAdapter<Activ
|
||||
pieChart.animateY(ANIM_TIME, animationEffect);
|
||||
}
|
||||
|
||||
private void setUpChart(PieChart DashboardChart) {
|
||||
DashboardChart.setNoDataText("");
|
||||
DashboardChart.getLegend().setEnabled(false);
|
||||
DashboardChart.setDrawHoleEnabled(true);
|
||||
DashboardChart.setHoleColor(Color.WHITE);
|
||||
DashboardChart.getDescription().setText("");
|
||||
DashboardChart.setTransparentCircleColor(Color.WHITE);
|
||||
DashboardChart.setTransparentCircleAlpha(110);
|
||||
DashboardChart.setHoleRadius(70f);
|
||||
DashboardChart.setTransparentCircleRadius(75f);
|
||||
DashboardChart.setDrawCenterText(true);
|
||||
DashboardChart.setRotationEnabled(true);
|
||||
DashboardChart.setHighlightPerTapEnabled(true);
|
||||
DashboardChart.setCenterTextOffset(0, 0);
|
||||
}
|
||||
|
||||
private float interpolate(float a, float b, float proportion) {
|
||||
return (a + ((b - a) * proportion));
|
||||
}
|
||||
@ -225,135 +125,162 @@ public class ActivityListingAdapter extends AbstractActivityListingAdapter<Activ
|
||||
return Color.HSVToColor(hsvb);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDateLabel(ActivitySession item) {
|
||||
return "";
|
||||
public static class ActivityItemViewHolder extends AbstractActivityListingViewHolder<ActivitySession> {
|
||||
final View rootView;
|
||||
final ActivityListItem activityListItem;
|
||||
|
||||
public ActivityItemViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
this.rootView = itemView;
|
||||
this.activityListItem = new ActivityListItem(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasGPS(ActivitySession item) {
|
||||
return false;
|
||||
public void fill(final int position, final ActivitySession session, final boolean selected) {
|
||||
this.activityListItem.update(
|
||||
session.getStartTime(),
|
||||
session.getEndTime(),
|
||||
session.getActivityKind(),
|
||||
null,
|
||||
session.getActiveSteps(),
|
||||
session.getDistance(),
|
||||
session.getHeartRateAverage(),
|
||||
session.getIntensity(),
|
||||
session.getEndTime().getTime() - session.getStartTime().getTime(),
|
||||
false,
|
||||
null,
|
||||
position % 2 == 1,
|
||||
selected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class DashboardViewHolder extends AbstractActivityListingViewHolder<ActivitySession> {
|
||||
final PieChart ActiveStepsChart;
|
||||
final PieChart DistanceChart;
|
||||
final PieChart ActiveTimeChart;
|
||||
final TextView stepLabel;
|
||||
final TextView stepTotalLabel;
|
||||
final TextView distanceLabel;
|
||||
final TextView hrLabel;
|
||||
final TextView intensityLabel;
|
||||
final TextView intensity2Label;
|
||||
final TextView durationLabel;
|
||||
final TextView sessionCountLabel;
|
||||
final LinearLayout durationLayout;
|
||||
final LinearLayout countLayout;
|
||||
final View hrLayout;
|
||||
final LinearLayout stepsLayout;
|
||||
final LinearLayout stepsTotalLayout;
|
||||
final LinearLayout distanceLayout;
|
||||
final View intensityLayout;
|
||||
final View intensity2Layout;
|
||||
|
||||
public DashboardViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
|
||||
ActiveStepsChart = itemView.findViewById(R.id.activity_dashboard_piechart1);
|
||||
DistanceChart = itemView.findViewById(R.id.activity_dashboard_piechart2);
|
||||
ActiveTimeChart = itemView.findViewById(R.id.activity_dashboard_piechart3);
|
||||
stepLabel = itemView.findViewById(R.id.line_layout_step_label);
|
||||
stepTotalLabel = itemView.findViewById(R.id.line_layout_total_step_label);
|
||||
distanceLabel = itemView.findViewById(R.id.line_layout_distance_label);
|
||||
hrLabel = itemView.findViewById(R.id.heartrate_widget_label);
|
||||
intensityLabel = itemView.findViewById(R.id.intensity_widget_label);
|
||||
intensity2Label = itemView.findViewById(R.id.line_layout_intensity2_label);
|
||||
durationLabel = itemView.findViewById(R.id.line_layout_duration_label);
|
||||
sessionCountLabel = itemView.findViewById(R.id.line_layout_count_label);
|
||||
durationLayout = itemView.findViewById(R.id.line_layout_duration);
|
||||
countLayout = itemView.findViewById(R.id.line_layout_count);
|
||||
hrLayout = itemView.findViewById(R.id.heartrate_widget_icon);
|
||||
stepsLayout = itemView.findViewById(R.id.line_layout_step);
|
||||
stepsTotalLayout = itemView.findViewById(R.id.line_layout_total_step);
|
||||
distanceLayout = itemView.findViewById(R.id.line_layout_distance);
|
||||
intensityLayout = itemView.findViewById(R.id.intensity_widget_icon);
|
||||
intensity2Layout = itemView.findViewById(R.id.line_layout_intensity2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasDate(ActivitySession item) {
|
||||
return false;
|
||||
public void fill(final int position, final ActivitySession session, final boolean selected) {
|
||||
setUpChart(ActiveStepsChart);
|
||||
int steps = session.getActiveSteps();
|
||||
setChartsData(ActiveStepsChart, steps, stepsGoal, getContext().getString(R.string.activity_list_summary_active_steps), getContext());
|
||||
|
||||
setUpChart(DistanceChart);
|
||||
float distance = session.getDistance();
|
||||
setChartsData(DistanceChart, distance, distanceGoalMeters, getContext().getString(R.string.distance), getContext());
|
||||
|
||||
setUpChart(ActiveTimeChart);
|
||||
long duration = session.getEndTime().getTime() - session.getStartTime().getTime();
|
||||
setChartsData(ActiveTimeChart, duration, activeTimeGoalTimeMillis, getContext().getString(R.string.activity_list_summary_active_time), getContext());
|
||||
|
||||
durationLabel.setText(DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS));
|
||||
sessionCountLabel.setText(String.valueOf(session.getSessionCount()));
|
||||
|
||||
if (session.getHeartRateAverage() > 0) {
|
||||
hrLabel.setText(String.valueOf(session.getHeartRateAverage()));
|
||||
hrLayout.setVisibility(View.VISIBLE);
|
||||
hrLabel.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
hrLayout.setVisibility(View.GONE);
|
||||
hrLabel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTimeFrom(ActivitySession item) {
|
||||
Date time = item.getStartTime();
|
||||
return DateTimeUtils.formatTime(time.getHours(), time.getMinutes());
|
||||
if (session.getIntensity() > 0) {
|
||||
final DecimalFormat df = new DecimalFormat("###");
|
||||
intensityLabel.setText(df.format(session.getIntensity()));
|
||||
intensity2Label.setText(df.format(session.getIntensity()));
|
||||
intensityLayout.setVisibility(View.VISIBLE);
|
||||
intensity2Layout.setVisibility(View.VISIBLE);
|
||||
intensityLabel.setVisibility(View.VISIBLE);
|
||||
intensity2Label.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
intensityLayout.setVisibility(View.GONE);
|
||||
intensity2Layout.setVisibility(View.GONE);
|
||||
intensityLabel.setVisibility(View.GONE);
|
||||
intensity2Label.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTimeTo(ActivitySession item) {
|
||||
Date time = item.getEndTime();
|
||||
return DateTimeUtils.formatTime(time.getHours(), time.getMinutes());
|
||||
if (session.getDistance() > 0) {
|
||||
distanceLabel.setText(FormatUtils.getFormattedDistanceLabel(session.getDistance()));
|
||||
distanceLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
distanceLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getActivityName(ActivitySession item) {
|
||||
return item.getActivityKind().getLabel(getContext());
|
||||
if (session.getActiveSteps() > 0) {
|
||||
stepLabel.setText(String.valueOf(session.getActiveSteps()));
|
||||
stepsLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
stepsLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStepLabel(ActivitySession item) {
|
||||
return String.valueOf(item.getActiveSteps());
|
||||
if (session.getTotalDaySteps() > 0) {
|
||||
stepTotalLabel.setText(String.valueOf(session.getTotalDaySteps()));
|
||||
stepsTotalLayout.setVisibility(View.VISIBLE);
|
||||
countLayout.setVisibility(View.VISIBLE);
|
||||
durationLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
stepsTotalLayout.setVisibility(View.GONE);
|
||||
countLayout.setVisibility(View.GONE);
|
||||
durationLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDistanceLabel(ActivitySession item) {
|
||||
return FormatUtils.getFormattedDistanceLabel(item.getDistance());
|
||||
private void setUpChart(PieChart DashboardChart) {
|
||||
DashboardChart.setNoDataText("");
|
||||
DashboardChart.getLegend().setEnabled(false);
|
||||
DashboardChart.setDrawHoleEnabled(true);
|
||||
DashboardChart.setHoleColor(Color.WHITE);
|
||||
DashboardChart.getDescription().setText("");
|
||||
DashboardChart.setTransparentCircleColor(Color.WHITE);
|
||||
DashboardChart.setTransparentCircleAlpha(110);
|
||||
DashboardChart.setHoleRadius(70f);
|
||||
DashboardChart.setTransparentCircleRadius(75f);
|
||||
DashboardChart.setDrawCenterText(true);
|
||||
DashboardChart.setRotationEnabled(true);
|
||||
DashboardChart.setHighlightPerTapEnabled(true);
|
||||
DashboardChart.setCenterTextOffset(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHrLabel(ActivitySession item) {
|
||||
return String.valueOf(item.getHeartRateAverage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getIntensityLabel(ActivitySession item) {
|
||||
DecimalFormat df = new DecimalFormat("###");
|
||||
return df.format(item.getIntensity());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDurationLabel(ActivitySession item) {
|
||||
long duration = item.getEndTime().getTime() - item.getStartTime().getTime();
|
||||
return DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSpeedLabel(ActivitySession item) {
|
||||
long duration = item.getEndTime().getTime() - item.getStartTime().getTime();
|
||||
double distanceMeters = item.getDistance();
|
||||
|
||||
double speed = distanceMeters * 1000 / duration;
|
||||
String unit = "###.#km/h";
|
||||
String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
|
||||
if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
|
||||
unit = "###.#mi/h";
|
||||
speed = speed * 0.6213712;
|
||||
}
|
||||
DecimalFormat df = new DecimalFormat(unit);
|
||||
return df.format(speed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSessionCountLabel(ActivitySession item) {
|
||||
return String.valueOf(item.getSessionCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasHR(ActivitySession item) {
|
||||
return item.getHeartRateAverage() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasIntensity(ActivitySession item) {
|
||||
return item.getIntensity() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasDistance(ActivitySession item) {
|
||||
return item.getDistance() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasSteps(ActivitySession item) {
|
||||
return item.getActiveSteps() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasTotalSteps(ActivitySession item) {
|
||||
return item.getTotalDaySteps() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSummary(ActivitySession item, int position) {
|
||||
int sessionType = item.getSessionType();
|
||||
return sessionType == SESSION_SUMMARY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEmptySession(ActivitySession item, int position) {
|
||||
int sessionType = item.getSessionType();
|
||||
return sessionType == SESSION_EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEmptySummary(ActivitySession item) {
|
||||
return item.getIsEmptySummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStepTotalLabel(ActivitySession item) {
|
||||
return String.valueOf(item.getTotalDaySteps());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getIcon(ActivitySession item) {
|
||||
return item.getActivityKind().getIcon();
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,11 @@ import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -48,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
|
||||
|
||||
public class ActivityListingChartFragment extends AbstractActivityChartFragment<ActivityListingChartFragment.MyChartsData> {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingChartFragment.class);
|
||||
@ -62,20 +64,18 @@ public class ActivityListingChartFragment extends AbstractActivityChartFragment<
|
||||
Bundle savedInstanceState) {
|
||||
rootView = inflater.inflate(R.layout.fragment_steps_list, container, false);
|
||||
getChartsHost().enableSwipeRefresh(false);
|
||||
ListView stepsList = rootView.findViewById(R.id.itemListView);
|
||||
RecyclerView stepsList = rootView.findViewById(R.id.itemListView);
|
||||
stepListAdapter = new ActivityListingAdapter(getContext());
|
||||
stepsList.setAdapter(stepListAdapter);
|
||||
stepsList.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
|
||||
stepsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
ActivitySession item = stepListAdapter.getItem(i);
|
||||
stepListAdapter.setOnItemClickListener(position -> {
|
||||
ActivitySession item = stepListAdapter.getItem(position);
|
||||
if (item.getSessionType() != ActivitySession.SESSION_SUMMARY) {
|
||||
int tsFrom = (int) (item.getStartTime().getTime() / 1000);
|
||||
int tsTo = (int) (item.getEndTime().getTime() / 1000);
|
||||
showDetail(tsFrom, tsTo, item, getChartsHost().getDevice());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
stepsDateView = rootView.findViewById(R.id.stepsDateView);
|
||||
@ -99,11 +99,15 @@ public class ActivityListingChartFragment extends AbstractActivityChartFragment<
|
||||
return "Steps list";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSingleDay() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(ChartsHost.REFRESH)) {
|
||||
if (ChartsHost.REFRESH.equals(action)) {
|
||||
// TODO: use LimitLines to visualize smart alarms?
|
||||
refresh();
|
||||
} else {
|
||||
@ -144,6 +148,7 @@ public class ActivityListingChartFragment extends AbstractActivityChartFragment<
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection RedundantIfStatement
|
||||
if (mcd.getStepSessions().toArray().length == 0) {
|
||||
getChartsHost().enableSwipeRefresh(true); //enable pull to refresh, might be needed
|
||||
} else {
|
||||
@ -184,15 +189,14 @@ public class ActivityListingChartFragment extends AbstractActivityChartFragment<
|
||||
|
||||
private void showOngoingActivitySnackbar(ActivitySession ongoingSession) {
|
||||
|
||||
String distanceLabel = stepListAdapter.getDistanceLabel(ongoingSession);
|
||||
String stepLabel = stepListAdapter.getStepLabel(ongoingSession);
|
||||
String durationLabel = stepListAdapter.getDurationLabel(ongoingSession);
|
||||
String hrLabel = stepListAdapter.getHrLabel(ongoingSession);
|
||||
String activityName = stepListAdapter.getActivityName(ongoingSession);
|
||||
int icon = stepListAdapter.getIcon(ongoingSession);
|
||||
String distanceLabel = FormatUtils.getFormattedDistanceLabel(ongoingSession.getDistance());
|
||||
String stepLabel = String.valueOf(ongoingSession.getActiveSteps());
|
||||
String durationLabel = DateTimeUtils.formatDurationHoursMinutes(ongoingSession.getEndTime().getTime() - ongoingSession.getStartTime().getTime(), TimeUnit.MILLISECONDS);
|
||||
String hrLabel = String.valueOf(ongoingSession.getHeartRateAverage());
|
||||
String activityName = ongoingSession.getActivityKind().getLabel(requireContext());
|
||||
int icon = ongoingSession.getActivityKind().getIcon();
|
||||
|
||||
String text = String.format("%s:\u00A0%s, %s:\u00A0%s, %s:\u00A0%s, %s:\u00A0%s", activityName, durationLabel, getString(R.string.heart_rate), hrLabel, getString(R.string.steps), stepLabel, getString(R.string.distance), distanceLabel);
|
||||
|
||||
final Snackbar snackbar = Snackbar.make(rootView, text, 1000 * 8);
|
||||
|
||||
View snackbarView = snackbar.getView();
|
||||
|
@ -28,6 +28,7 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
@ -37,6 +38,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -49,7 +51,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialDialogFragment;
|
||||
|
||||
public class ActivityListingDashboard extends MaterialDialogFragment {
|
||||
@ -82,13 +84,11 @@ public class ActivityListingDashboard extends MaterialDialogFragment {
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
return inflater.inflate(R.layout.activity_list_total_dashboard, container);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
int time = getArguments().getInt("time", 1);
|
||||
@ -106,10 +106,10 @@ public class ActivityListingDashboard extends MaterialDialogFragment {
|
||||
}
|
||||
stepListAdapter = new ActivityListingAdapter(getContext());
|
||||
|
||||
final TextView battery_status_date_from_text = (TextView) getView().findViewById(R.id.battery_status_date_from_text);
|
||||
final TextView battery_status_date_to_text = (TextView) getView().findViewById(R.id.battery_status_date_to_text);
|
||||
LinearLayout battery_status_date_to_layout = (LinearLayout) getView().findViewById(R.id.battery_status_date_to_layout);
|
||||
final SeekBar battery_status_time_span_seekbar = (SeekBar) getView().findViewById(R.id.battery_status_time_span_seekbar);
|
||||
final TextView battery_status_date_from_text = getView().findViewById(R.id.battery_status_date_from_text);
|
||||
final TextView battery_status_date_to_text = getView().findViewById(R.id.battery_status_date_to_text);
|
||||
LinearLayout battery_status_date_to_layout = getView().findViewById(R.id.battery_status_date_to_layout);
|
||||
final SeekBar battery_status_time_span_seekbar = getView().findViewById(R.id.battery_status_time_span_seekbar);
|
||||
|
||||
boolean activity_list_debug_extra_time_range_value = GBApplication.getPrefs().getPreferences().getBoolean("activity_list_debug_extra_time_range", false);
|
||||
|
||||
@ -161,7 +161,7 @@ public class ActivityListingDashboard extends MaterialDialogFragment {
|
||||
battery_status_time_span_text.setText(text);
|
||||
battery_status_date_from_text.setText(DateTimeUtils.formatDate(new Date(timeFrom * 1000L)));
|
||||
battery_status_date_to_text.setText(DateTimeUtils.formatDate(new Date(timeTo * 1000L)));
|
||||
createRefreshTask("Visualizing data", getActivity()).execute();
|
||||
createRefreshTask("Visualizing step sessions", getActivity()).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -236,8 +236,12 @@ public class ActivityListingDashboard extends MaterialDialogFragment {
|
||||
}
|
||||
|
||||
void indicate_progress(boolean inProgress) {
|
||||
LinearLayout activity_list_dashboard_results_layout = getView().findViewById(R.id.activity_list_dashboard_results_layout);
|
||||
RelativeLayout activity_list_dashboard_loading_layout = getView().findViewById(R.id.activity_list_dashboard_loading_layout);
|
||||
View view = getView();
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
LinearLayout activity_list_dashboard_results_layout = view.findViewById(R.id.activity_list_dashboard_results_layout);
|
||||
RelativeLayout activity_list_dashboard_loading_layout = view.findViewById(R.id.activity_list_dashboard_loading_layout);
|
||||
if (inProgress) {
|
||||
activity_list_dashboard_results_layout.setVisibility(View.GONE);
|
||||
activity_list_dashboard_loading_layout.setVisibility(View.VISIBLE);
|
||||
@ -248,43 +252,49 @@ public class ActivityListingDashboard extends MaterialDialogFragment {
|
||||
}
|
||||
|
||||
void populateData(ActivitySession item) {
|
||||
TextView stepLabel = getView().findViewById(R.id.line_layout_step_label);
|
||||
TextView stepTotalLabel = getView().findViewById(R.id.line_layout_total_step_label);
|
||||
TextView distanceLabel = getView().findViewById(R.id.line_layout_distance_label);
|
||||
TextView durationLabel = getView().findViewById(R.id.line_layout_duration_label);
|
||||
TextView sessionCountLabel = getView().findViewById(R.id.line_layout_count_label);
|
||||
LinearLayout durationLayout = getView().findViewById(R.id.line_layout_duration);
|
||||
LinearLayout countLayout = getView().findViewById(R.id.line_layout_count);
|
||||
LinearLayout stepsLayout = getView().findViewById(R.id.line_layout_step);
|
||||
LinearLayout stepsTotalLayout = getView().findViewById(R.id.line_layout_total_step);
|
||||
LinearLayout distanceLayout = getView().findViewById(R.id.line_layout_distance);
|
||||
View view = getView();
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
stepLabel.setText(stepListAdapter.getStepLabel(item));
|
||||
stepTotalLabel.setText(stepListAdapter.getStepTotalLabel(item));
|
||||
distanceLabel.setText(stepListAdapter.getDistanceLabel(item));
|
||||
durationLabel.setText(stepListAdapter.getDurationLabel(item));
|
||||
sessionCountLabel.setText(stepListAdapter.getSessionCountLabel(item));
|
||||
TextView stepLabel = view.findViewById(R.id.line_layout_step_label);
|
||||
TextView stepTotalLabel = view.findViewById(R.id.line_layout_total_step_label);
|
||||
TextView distanceLabel = view.findViewById(R.id.line_layout_distance_label);
|
||||
TextView durationLabel = view.findViewById(R.id.line_layout_duration_label);
|
||||
TextView sessionCountLabel = view.findViewById(R.id.line_layout_count_label);
|
||||
LinearLayout durationLayout = view.findViewById(R.id.line_layout_duration);
|
||||
LinearLayout countLayout = view.findViewById(R.id.line_layout_count);
|
||||
LinearLayout stepsLayout = view.findViewById(R.id.line_layout_step);
|
||||
LinearLayout stepsTotalLayout = view.findViewById(R.id.line_layout_total_step);
|
||||
LinearLayout distanceLayout = view.findViewById(R.id.line_layout_distance);
|
||||
|
||||
if (!stepListAdapter.hasDistance(item)) {
|
||||
distanceLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
final long duration = item.getEndTime().getTime() - item.getStartTime().getTime();
|
||||
durationLabel.setText(DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS));
|
||||
sessionCountLabel.setText(String.valueOf(item.getSessionCount()));
|
||||
|
||||
if (item.getDistance() > 0) {
|
||||
distanceLabel.setText(FormatUtils.getFormattedDistanceLabel(item.getDistance()));
|
||||
distanceLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
distanceLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (!stepListAdapter.hasSteps(item)) {
|
||||
stepsLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (item.getActiveSteps() > 0) {
|
||||
stepLabel.setText(String.valueOf(item.getActiveSteps()));
|
||||
stepsLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
stepsLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (!stepListAdapter.hasTotalSteps(item)) {
|
||||
stepsTotalLayout.setVisibility(View.GONE);
|
||||
countLayout.setVisibility(View.GONE);
|
||||
durationLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (item.getTotalDaySteps() > 0) {
|
||||
stepTotalLabel.setText(String.valueOf(item.getTotalDaySteps()));
|
||||
stepsTotalLayout.setVisibility(View.VISIBLE);
|
||||
countLayout.setVisibility(View.VISIBLE);
|
||||
durationLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
stepsTotalLayout.setVisibility(View.GONE);
|
||||
countLayout.setVisibility(View.GONE);
|
||||
durationLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesChartFragment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityListItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
|
||||
|
||||
public class ActivityListingDetail extends DialogFragment {
|
||||
@ -85,6 +86,21 @@ public class ActivityListingDetail extends DialogFragment {
|
||||
|
||||
ActivityListingAdapter stepListAdapter = new ActivityListingAdapter(getContext());
|
||||
View activityItem = view.findViewById(R.id.activityItemHolder);
|
||||
stepListAdapter.fill_item(item, 0, activityItem, null);
|
||||
ActivityListItem activityListItem = new ActivityListItem(activityItem);
|
||||
activityListItem.update(
|
||||
item.getStartTime(),
|
||||
item.getEndTime(),
|
||||
item.getActivityKind(),
|
||||
null,
|
||||
item.getActiveSteps(),
|
||||
item.getDistance(),
|
||||
item.getHeartRateAverage(),
|
||||
item.getIntensity(),
|
||||
item.getEndTime().getTime() - item.getStartTime().getTime(),
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
@ -17,228 +17,108 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
|
||||
|
||||
/**
|
||||
* Adapter for displaying generic ItemWithDetails instances.
|
||||
*/
|
||||
public abstract class AbstractActivityListingAdapter<T> extends ArrayAdapter<T> {
|
||||
|
||||
public abstract class AbstractActivityListingAdapter<T> extends RecyclerView.Adapter<AbstractActivityListingAdapter.AbstractActivityListingViewHolder<T>> {
|
||||
private final Context context;
|
||||
private final List<T> items;
|
||||
private final int SESSION_SUMMARY = ActivitySession.SESSION_SUMMARY;
|
||||
private int backgroundColor = 0;
|
||||
private int alternateColor = 0;
|
||||
private boolean zebraStripes = true;
|
||||
private boolean showTime = true;
|
||||
private final BitSet selectedItems = new BitSet();
|
||||
|
||||
private OnItemClickListener onItemSingleClickListener;
|
||||
private OnItemClickListener onItemLongClickListener;
|
||||
|
||||
public AbstractActivityListingAdapter(Context context) {
|
||||
this(context, new ArrayList<T>());
|
||||
}
|
||||
|
||||
public AbstractActivityListingAdapter(Context context, List<T> items) {
|
||||
super(context, 0, items);
|
||||
|
||||
this.context = context;
|
||||
this.items = items;
|
||||
alternateColor = getAlternateColor(context);
|
||||
this.items = new ArrayList<T>();
|
||||
}
|
||||
|
||||
public static int getAlternateColor(Context context) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(R.attr.alternate_row_background, typedValue, true);
|
||||
return typedValue.data;
|
||||
public void setOnItemClickListener(final OnItemClickListener onItemSingleClickListener) {
|
||||
this.onItemSingleClickListener = onItemSingleClickListener;
|
||||
}
|
||||
|
||||
public void setOnItemLongClickListener(final OnItemClickListener onItemLongClickListener) {
|
||||
this.onItemLongClickListener = onItemLongClickListener;
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public BitSet getSelectedItems() {
|
||||
return selectedItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
T item = getItem(position);
|
||||
|
||||
if (isSummary(item, position)) {
|
||||
view = fill_dashboard(item, position, view, parent, context);
|
||||
} else if (isEmptySession(item, position)) {
|
||||
view = fill_empty(parent);
|
||||
} else {
|
||||
view = fill_item(item, position, view, parent);
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
return view;
|
||||
public T getItem(final int position) {
|
||||
return items.get(position);
|
||||
}
|
||||
|
||||
public View fill_item(T item, int position, View view, ViewGroup parent) {
|
||||
if (parent != null) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.activity_list_item, parent, false);
|
||||
}
|
||||
TextView timeFrom = view.findViewById(R.id.line_layout_time_from);
|
||||
TextView timeTo = view.findViewById(R.id.line_layout_time_to);
|
||||
TextView activityName = view.findViewById(R.id.line_layout_activity_name);
|
||||
TextView stepLabel = view.findViewById(R.id.line_layout_step_label);
|
||||
TextView distanceLabel = view.findViewById(R.id.line_layout_distance_label);
|
||||
TextView hrLabel = view.findViewById(R.id.line_layout_hr_label);
|
||||
TextView intensityLabel = view.findViewById(R.id.line_layout_intensity_label);
|
||||
TextView durationLabel = view.findViewById(R.id.line_layout_duration_label);
|
||||
TextView dateLabel = view.findViewById(R.id.line_layout_date_label);
|
||||
|
||||
LinearLayout timeLayout = view.findViewById(R.id.line_layout_time);
|
||||
LinearLayout hrLayout = view.findViewById(R.id.line_layout_hr);
|
||||
LinearLayout stepsLayout = view.findViewById(R.id.line_layout_step);
|
||||
LinearLayout distanceLayout = view.findViewById(R.id.line_layout_distance);
|
||||
LinearLayout intensityLayout = view.findViewById(R.id.line_layout_intensity);
|
||||
LinearLayout dateLayout = view.findViewById(R.id.line_layout_date);
|
||||
LinearLayout list_item_subparent_layout = view.findViewById(R.id.list_item_subparent_layout);
|
||||
|
||||
RelativeLayout parentLayout = view.findViewById(R.id.list_item_parent_layout);
|
||||
|
||||
ImageView activityIcon = view.findViewById(R.id.line_layout_activity_icon);
|
||||
ImageView gpsIcon = view.findViewById(R.id.line_layout_gps_icon);
|
||||
|
||||
timeFrom.setText(getTimeFrom(item));
|
||||
timeTo.setText(getTimeTo(item));
|
||||
activityName.setText(getActivityName(item));
|
||||
stepLabel.setText(getStepLabel(item));
|
||||
distanceLabel.setText(getDistanceLabel(item));
|
||||
hrLabel.setText(getHrLabel(item));
|
||||
intensityLabel.setText(getIntensityLabel(item));
|
||||
durationLabel.setText(getDurationLabel(item));
|
||||
dateLabel.setText(getDateLabel(item));
|
||||
|
||||
|
||||
if (!hasHR(item)) {
|
||||
hrLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
hrLayout.setVisibility(View.VISIBLE);
|
||||
public int getPosition(final T item) {
|
||||
return items.indexOf(item);
|
||||
}
|
||||
|
||||
if (!hasIntensity(item)) {
|
||||
intensityLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
intensityLayout.setVisibility(View.VISIBLE);
|
||||
public boolean isSelected(final int position) {
|
||||
return selectedItems.get(position);
|
||||
}
|
||||
|
||||
if (!hasDistance(item)) {
|
||||
distanceLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
distanceLayout.setVisibility(View.VISIBLE);
|
||||
@Override
|
||||
public int getItemViewType(final int position) {
|
||||
if (position == 0) {
|
||||
// First item is always the dashboard
|
||||
return 0;
|
||||
} else if (position == getItemCount() - 1) {
|
||||
// Last item is always an empty session (prevent overlap with fab)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!hasSteps(item)) {
|
||||
stepsLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
stepsLayout.setVisibility(View.VISIBLE);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (!hasDate(item)) {
|
||||
dateLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
dateLayout.setVisibility(View.VISIBLE);
|
||||
@NonNull
|
||||
@Override
|
||||
public AbstractActivityListingViewHolder<T> onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||
switch (viewType) {
|
||||
case 1: // empty
|
||||
return new EmptyViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_item, parent, false));
|
||||
}
|
||||
|
||||
if (!showTime) {
|
||||
timeLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
timeLayout.setVisibility(View.VISIBLE);
|
||||
throw new IllegalArgumentException("Unknown view type " + viewType);
|
||||
}
|
||||
|
||||
if (!hasGPS(item)) {
|
||||
gpsIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
gpsIcon.setVisibility(View.VISIBLE);
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final AbstractActivityListingViewHolder<T> holder, int position) {
|
||||
holder.fill(position, getItem(position), isSelected(position));
|
||||
if (position > 0 && position < getItemCount() - 1) {
|
||||
if (onItemSingleClickListener != null) {
|
||||
holder.itemView.setOnClickListener(v -> onItemSingleClickListener.onClick(position));
|
||||
}
|
||||
activityIcon.setImageResource(getIcon(item));
|
||||
|
||||
if (zebraStripes && position % 2 != 0) {
|
||||
parentLayout.setBackgroundColor(alternateColor);
|
||||
if (onItemLongClickListener != null) {
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
onItemLongClickListener.onClick(position);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private View fill_empty(ViewGroup parent) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.activity_list_item, parent, false);
|
||||
view.setVisibility(View.GONE);
|
||||
return view;
|
||||
}
|
||||
|
||||
protected abstract View fill_dashboard(T item, int position, View view, ViewGroup parent, Context context);
|
||||
|
||||
protected abstract String getDateLabel(T item);
|
||||
|
||||
protected abstract boolean hasGPS(T item);
|
||||
|
||||
protected abstract boolean hasDate(T item);
|
||||
|
||||
protected abstract String getTimeFrom(T item);
|
||||
|
||||
protected abstract String getTimeTo(T item);
|
||||
|
||||
protected abstract String getActivityName(T item);
|
||||
|
||||
protected abstract String getStepLabel(T item);
|
||||
|
||||
protected abstract String getDistanceLabel(T item);
|
||||
|
||||
protected abstract String getHrLabel(T item);
|
||||
|
||||
protected abstract String getIntensityLabel(T item);
|
||||
|
||||
protected abstract String getDurationLabel(T item);
|
||||
|
||||
protected abstract String getSpeedLabel(T item);
|
||||
|
||||
protected abstract String getSessionCountLabel(T item);
|
||||
|
||||
protected abstract boolean hasHR(T item);
|
||||
|
||||
protected abstract boolean hasIntensity(T item);
|
||||
|
||||
protected abstract boolean hasDistance(T item);
|
||||
|
||||
protected abstract boolean hasSteps(T item);
|
||||
|
||||
protected abstract boolean hasTotalSteps(T item);
|
||||
|
||||
protected abstract boolean isSummary(T item, int position);
|
||||
|
||||
protected abstract boolean isEmptySession(T item, int position);
|
||||
|
||||
protected abstract boolean isEmptySummary(T item);
|
||||
|
||||
protected abstract String getStepTotalLabel(T item);
|
||||
|
||||
public void setZebraStripes(boolean enable) {
|
||||
zebraStripes = enable;
|
||||
}
|
||||
|
||||
public void setShowTime(boolean enable) {
|
||||
showTime = enable;
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
protected abstract int getIcon(T item);
|
||||
|
||||
public List<T> getItems() {
|
||||
return items;
|
||||
}
|
||||
@ -249,13 +129,13 @@ public abstract class AbstractActivityListingAdapter<T> extends ArrayAdapter<T>
|
||||
public void setItems(List<T> items, boolean notify) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
this.selectedItems.clear();
|
||||
if (notify) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void setActivityKindFilter(int activityKind) {
|
||||
this.setActivityKindFilter(activityKind);
|
||||
}
|
||||
|
||||
public void setDateFromFilter(long date) {
|
||||
@ -267,9 +147,35 @@ public abstract class AbstractActivityListingAdapter<T> extends ArrayAdapter<T>
|
||||
public void setNameContainsFilter(String name) {
|
||||
}
|
||||
|
||||
public void setItemsFilter(List items) {
|
||||
public void setItemsFilter(List<Long> items) {
|
||||
}
|
||||
|
||||
public void setDeviceFilter(long device) {
|
||||
}
|
||||
|
||||
public abstract static class AbstractActivityListingViewHolder<T> extends RecyclerView.ViewHolder {
|
||||
public AbstractActivityListingViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public abstract void fill(int position, T item, final boolean selected);
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onClick(int position);
|
||||
}
|
||||
|
||||
public class EmptyViewHolder extends AbstractActivityListingViewHolder<T> {
|
||||
final View rootView;
|
||||
|
||||
public EmptyViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
this.rootView = itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(final int position, final T item, final boolean selected) {
|
||||
rootView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,15 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -46,7 +47,9 @@ import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityListItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
@ -54,6 +57,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesFilter.ALL_DEVICES;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.INTERNAL_HAS_GPS;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class ActivitySummariesAdapter extends AbstractActivityListingAdapter<BaseActivitySummary> {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesAdapter.class);
|
||||
@ -64,7 +73,6 @@ public class ActivitySummariesAdapter extends AbstractActivityListingAdapter<Bas
|
||||
String nameContainsFilter;
|
||||
List<Long> itemsFilter;
|
||||
private int activityKindFilter;
|
||||
private int backgroundColor = 0;
|
||||
|
||||
public ActivitySummariesAdapter(Context context, GBDevice device, int activityKindFilter, long dateFromFilter, long dateToFilter, String nameContainsFilter, long deviceFilter, List itemsFilter) {
|
||||
super(context);
|
||||
@ -111,7 +119,7 @@ public class ActivitySummariesAdapter extends AbstractActivityListingAdapter<Bas
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.EndTime.lt(new Date(dateToFilter)));
|
||||
}
|
||||
if (nameContainsFilter != null && nameContainsFilter.length() > 0) {
|
||||
if (nameContainsFilter != null && !nameContainsFilter.isEmpty()) {
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.Name.like("%" + nameContainsFilter + "%"));
|
||||
}
|
||||
@ -122,14 +130,35 @@ public class ActivitySummariesAdapter extends AbstractActivityListingAdapter<Bas
|
||||
|
||||
|
||||
List<BaseActivitySummary> allSummaries = new ArrayList<>();
|
||||
allSummaries.add(new BaseActivitySummary());
|
||||
allSummaries.addAll(qb.build().list());
|
||||
// HACK: Populate json in a dummy summary, so stats load faster
|
||||
BaseActivitySummary dashboardSummary = new BaseActivitySummary();
|
||||
final List<BaseActivitySummary> summaries = qb.build().list();
|
||||
dashboardSummary.setSummaryData(StatsContainer.from(
|
||||
device.getDeviceCoordinator().getActivitySummaryParser(device, getContext()),
|
||||
summaries
|
||||
).toJson());
|
||||
allSummaries.add(dashboardSummary); // dashboard
|
||||
allSummaries.addAll(summaries);
|
||||
allSummaries.add(new BaseActivitySummary()); // empty
|
||||
setItems(allSummaries, true);
|
||||
} catch (Exception e) {
|
||||
GB.toast("Error loading activity summaries.", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AbstractActivityListingViewHolder<BaseActivitySummary> onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||
switch (viewType) {
|
||||
case 0: // dashboard
|
||||
return new DashboardViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_summary_dashboard_item, parent, false));
|
||||
case 2: // item
|
||||
return new ActivityItemViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_item, parent, false));
|
||||
}
|
||||
|
||||
return super.onCreateViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
public void setActivityKindFilter(int filter) {
|
||||
this.activityKindFilter = filter;
|
||||
}
|
||||
@ -146,7 +175,7 @@ public class ActivitySummariesAdapter extends AbstractActivityListingAdapter<Bas
|
||||
this.nameContainsFilter = name;
|
||||
}
|
||||
|
||||
public void setItemsFilter(List items) {
|
||||
public void setItemsFilter(List<Long> items) {
|
||||
this.itemsFilter = items;
|
||||
}
|
||||
|
||||
@ -158,52 +187,173 @@ public class ActivitySummariesAdapter extends AbstractActivityListingAdapter<Bas
|
||||
return this.activityKindFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View fill_dashboard(BaseActivitySummary item, int position, View view, ViewGroup parent, Context context) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.activity_summary_dashboard_item, parent, false);
|
||||
public static class ActivityItemViewHolder extends AbstractActivityListingViewHolder<BaseActivitySummary> {
|
||||
final View rootView;
|
||||
final ActivityListItem activityListItem;
|
||||
|
||||
public ActivityItemViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
this.rootView = itemView;
|
||||
this.activityListItem = new ActivityListItem(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(final int position, final BaseActivitySummary summary, final boolean selected) {
|
||||
final boolean hasGps;
|
||||
|
||||
if (summary.getGpxTrack() != null) {
|
||||
hasGps = true;
|
||||
} else if (summary.getSummaryData() != null && summary.getSummaryData().contains(ActivitySummaryEntries.INTERNAL_HAS_GPS)) {
|
||||
final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(summary.getSummaryData());
|
||||
hasGps = summaryData != null && summaryData.getBoolean(INTERNAL_HAS_GPS, false);
|
||||
} else {
|
||||
hasGps = false;
|
||||
}
|
||||
|
||||
this.activityListItem.update(
|
||||
null,
|
||||
null,
|
||||
ActivityKind.fromCode(summary.getActivityKind()),
|
||||
summary.getName(),
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
summary.getEndTime().getTime() - summary.getStartTime().getTime(),
|
||||
hasGps,
|
||||
summary.getStartTime(),
|
||||
position % 2 == 1,
|
||||
selected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class DashboardViewHolder extends AbstractActivityListingViewHolder<BaseActivitySummary> {
|
||||
final TextView durationSumView;
|
||||
final TextView caloriesBurntSumView;
|
||||
final TextView distanceSumView;
|
||||
final TextView activeSecondsSumView;
|
||||
final TextView timeStartView;
|
||||
final TextView timeEndView;
|
||||
final TextView activitiesCountView;
|
||||
final TextView activityKindView;
|
||||
final ImageView activityIconView;
|
||||
final ImageView activityIconBigView;
|
||||
|
||||
public DashboardViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
|
||||
durationSumView = itemView.findViewById(R.id.summary_dashboard_layout_duration_label);
|
||||
caloriesBurntSumView = itemView.findViewById(R.id.summary_dashboard_layout_calories_label);
|
||||
distanceSumView = itemView.findViewById(R.id.summary_dashboard_layout_distance_label);
|
||||
activeSecondsSumView = itemView.findViewById(R.id.summary_dashboard_layout_active_duration_label);
|
||||
timeStartView = itemView.findViewById(R.id.summary_dashboard_layout_from_label);
|
||||
timeEndView = itemView.findViewById(R.id.summary_dashboard_layout_to_label);
|
||||
activitiesCountView = itemView.findViewById(R.id.summary_dashboard_layout_count_label);
|
||||
activityKindView = itemView.findViewById(R.id.summary_dashboard_layout_activity_label);
|
||||
activityIconView = itemView.findViewById(R.id.summary_dashboard_layout_activity_icon);
|
||||
activityIconBigView = itemView.findViewById(R.id.summary_dashboard_layout_big_activity_icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(final int position, final BaseActivitySummary summary, final boolean selected) {
|
||||
int activitiesCount = getItemCount() - 2; // remove dashboard and end spacer
|
||||
|
||||
final DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
|
||||
String summaryData = summary.getSummaryData();
|
||||
final StatsContainer stats;
|
||||
if (StringUtils.isNotBlank(summaryData)) {
|
||||
stats = StatsContainer.fromJson(summaryData);
|
||||
} else {
|
||||
stats = StatsContainer.from(
|
||||
coordinator.getActivitySummaryParser(device, getContext()),
|
||||
getItems()
|
||||
);
|
||||
}
|
||||
|
||||
DecimalFormat df = new DecimalFormat("#.##");
|
||||
durationSumView.setText(String.format("%s", DateTimeUtils.formatDurationHoursMinutes((long) stats.durationSum, TimeUnit.MILLISECONDS)));
|
||||
caloriesBurntSumView.setText(String.format("%s %s", (long) stats.caloriesBurntSum, getContext().getString(R.string.calories_unit)));
|
||||
distanceSumView.setText(String.format("%s %s", df.format(stats.distanceSum / 1000), getContext().getString(R.string.km)));
|
||||
distanceSumView.setText(FormatUtils.getFormattedDistanceLabel(stats.distanceSum));
|
||||
|
||||
activeSecondsSumView.setText(String.format("%s", DateTimeUtils.formatDurationHoursMinutes((long) stats.activeSecondsSum, TimeUnit.SECONDS)));
|
||||
activitiesCountView.setText(String.valueOf(activitiesCount));
|
||||
String activityName = getContext().getString(R.string.activity_summaries_all_activities);
|
||||
if (gettActivityKindFilter() != 0) {
|
||||
ActivityKind activityKind = ActivityKind.fromCode(gettActivityKindFilter());
|
||||
activityName = activityKind.getLabel(getContext());
|
||||
activityIconView.setImageResource(activityKind.getIcon());
|
||||
activityIconBigView.setImageResource(activityKind.getIcon());
|
||||
} else if (stats.activityIcon != 0) {
|
||||
ActivityKind activityKind = ActivityKind.fromCode(stats.activityIcon);
|
||||
activityIconView.setImageResource(activityKind.getIcon());
|
||||
activityIconBigView.setImageResource(activityKind.getIcon());
|
||||
}
|
||||
|
||||
activityKindView.setText(activityName);
|
||||
|
||||
//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) stats.lastItemDate)));
|
||||
timeEndView.setText((dateToFilter != 0) ? DateTimeUtils.formatDate(new Date(dateToFilter)) : DateTimeUtils.formatDate(new Date((long) stats.firstItemDate)));
|
||||
}
|
||||
}
|
||||
|
||||
private static class StatsContainer {
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
|
||||
private final double durationSum;
|
||||
private final double caloriesBurntSum;
|
||||
private final double distanceSum;
|
||||
private final double activeSecondsSum;
|
||||
private final double firstItemDate;
|
||||
private final double lastItemDate;
|
||||
private final int activityIcon;
|
||||
|
||||
public StatsContainer(final double durationSum,
|
||||
final double caloriesBurntSum,
|
||||
final double distanceSum,
|
||||
final double activeSecondsSum,
|
||||
final double firstItemDate,
|
||||
final double lastItemDate,
|
||||
final int activityIcon) {
|
||||
this.durationSum = durationSum;
|
||||
this.caloriesBurntSum = caloriesBurntSum;
|
||||
this.distanceSum = distanceSum;
|
||||
this.activeSecondsSum = activeSecondsSum;
|
||||
this.firstItemDate = firstItemDate;
|
||||
this.lastItemDate = lastItemDate;
|
||||
this.activityIcon = activityIcon;
|
||||
}
|
||||
|
||||
private static StatsContainer from(final ActivitySummaryParser summaryParser,
|
||||
final List<BaseActivitySummary> activities) {
|
||||
double durationSum = 0;
|
||||
double caloriesBurntSum = 0;
|
||||
double distanceSum = 0;
|
||||
double activeSecondsSum = 0;
|
||||
double firstItemDate = 0;
|
||||
double lastItemDate = 0;
|
||||
int activitiesCount = getCount() - 1;
|
||||
int activityIcon = 0;
|
||||
boolean activitySame = true;
|
||||
|
||||
TextView durationSumView = view.findViewById(R.id.summary_dashboard_layout_duration_label);
|
||||
TextView caloriesBurntSumView = view.findViewById(R.id.summary_dashboard_layout_calories_label);
|
||||
TextView distanceSumView = view.findViewById(R.id.summary_dashboard_layout_distance_label);
|
||||
TextView activeSecondsSumView = view.findViewById(R.id.summary_dashboard_layout_active_duration_label);
|
||||
TextView timeStartView = view.findViewById(R.id.summary_dashboard_layout_from_label);
|
||||
TextView timeEndView = view.findViewById(R.id.summary_dashboard_layout_to_label);
|
||||
TextView activitiesCountView = view.findViewById(R.id.summary_dashboard_layout_count_label);
|
||||
TextView activityKindView = view.findViewById(R.id.summary_dashboard_layout_activity_label);
|
||||
ImageView activityIconView = view.findViewById(R.id.summary_dashboard_layout_activity_icon);
|
||||
ImageView activityIconBigView = view.findViewById(R.id.summary_dashboard_layout_big_activity_icon);
|
||||
for (BaseActivitySummary summary : activities) {
|
||||
if (summary.getStartTime() == null) continue; //first item is empty, for dashboard
|
||||
|
||||
final DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
|
||||
for (BaseActivitySummary sportitem : getItems()) {
|
||||
if (sportitem.getStartTime() == null) continue; //first item is empty, for dashboard
|
||||
|
||||
if (firstItemDate == 0) firstItemDate = sportitem.getStartTime().getTime();
|
||||
lastItemDate = sportitem.getEndTime().getTime();
|
||||
durationSum += sportitem.getEndTime().getTime() - sportitem.getStartTime().getTime();
|
||||
if (firstItemDate == 0) firstItemDate = summary.getStartTime().getTime();
|
||||
lastItemDate = summary.getEndTime().getTime();
|
||||
durationSum += summary.getEndTime().getTime() - summary.getStartTime().getTime();
|
||||
|
||||
if (activityIcon == 0) {
|
||||
activityIcon = sportitem.getActivityKind();
|
||||
activityIcon = summary.getActivityKind();
|
||||
} else {
|
||||
if (activityIcon != sportitem.getActivityKind()) {
|
||||
if (activityIcon != summary.getActivityKind()) {
|
||||
activitySame = false;
|
||||
}
|
||||
}
|
||||
|
||||
final ActivitySummaryParser summaryParser = coordinator.getActivitySummaryParser(device, getContext());
|
||||
final ActivitySummaryJsonSummary activitySummaryJsonSummary = new ActivitySummaryJsonSummary(summaryParser, sportitem);
|
||||
final ActivitySummaryJsonSummary activitySummaryJsonSummary = new ActivitySummaryJsonSummary(summaryParser, summary);
|
||||
ActivitySummaryData summarySubdata = activitySummaryJsonSummary.getSummaryData(false);
|
||||
|
||||
if (summarySubdata != null) {
|
||||
@ -218,180 +368,24 @@ public class ActivitySummariesAdapter extends AbstractActivityListingAdapter<Bas
|
||||
}
|
||||
}
|
||||
}
|
||||
DecimalFormat df = new DecimalFormat("#.##");
|
||||
durationSumView.setText(String.format("%s", DateTimeUtils.formatDurationHoursMinutes((long) durationSum, TimeUnit.MILLISECONDS)));
|
||||
caloriesBurntSumView.setText(String.format("%s %s", (long) caloriesBurntSum, context.getString(R.string.calories_unit)));
|
||||
distanceSumView.setText(String.format("%s %s", df.format(distanceSum / 1000), context.getString(R.string.km)));
|
||||
distanceSumView.setText(FormatUtils.getFormattedDistanceLabel(distanceSum));
|
||||
|
||||
activeSecondsSumView.setText(String.format("%s", DateTimeUtils.formatDurationHoursMinutes((long) activeSecondsSum, TimeUnit.SECONDS)));
|
||||
activitiesCountView.setText(String.valueOf(activitiesCount));
|
||||
String activityName = context.getString(R.string.activity_summaries_all_activities);
|
||||
if (gettActivityKindFilter() != 0) {
|
||||
ActivityKind activityKind = ActivityKind.fromCode(gettActivityKindFilter());
|
||||
activityName = activityKind.getLabel(context);
|
||||
activityIconView.setImageResource(activityKind.getIcon());
|
||||
activityIconBigView.setImageResource(activityKind.getIcon());
|
||||
} else {
|
||||
if (activitySame) {
|
||||
ActivityKind activityKind = ActivityKind.fromCode(activityIcon);
|
||||
activityIconView.setImageResource(activityKind.getIcon());
|
||||
activityIconBigView.setImageResource(activityKind.getIcon());
|
||||
}
|
||||
return new StatsContainer(
|
||||
durationSum,
|
||||
caloriesBurntSum,
|
||||
distanceSum,
|
||||
activeSecondsSum,
|
||||
firstItemDate,
|
||||
lastItemDate,
|
||||
activitySame ? activityIcon : 0
|
||||
);
|
||||
}
|
||||
|
||||
activityKindView.setText(activityName);
|
||||
|
||||
//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)));
|
||||
return view;
|
||||
public static StatsContainer fromJson(final String json) {
|
||||
return GSON.fromJson(json, StatsContainer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDateLabel(BaseActivitySummary item) {
|
||||
Date startTime = item.getStartTime();
|
||||
String separator = ",";
|
||||
if (startTime != null) {
|
||||
String activityDay;
|
||||
|
||||
if (DateUtils.isToday(startTime.getTime())) {
|
||||
activityDay = getContext().getString(R.string.activity_summary_today);
|
||||
} else if (DateTimeUtils.isYesterday(startTime)) {
|
||||
activityDay = getContext().getString(R.string.activity_summary_yesterday);
|
||||
} else {
|
||||
activityDay = DateTimeUtils.formatDate(startTime, DateUtils.FORMAT_SHOW_WEEKDAY);
|
||||
public String toJson() {
|
||||
return GSON.toJson(this);
|
||||
}
|
||||
String activityTime = DateTimeUtils.formatTime(startTime.getHours(), startTime.getMinutes());
|
||||
return String.format("%s%s %s", activityDay, separator, activityTime);
|
||||
}
|
||||
return "Unknown time";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasGPS(BaseActivitySummary item) {
|
||||
if (item.getGpxTrack() != null) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasDate(BaseActivitySummary item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTimeFrom(BaseActivitySummary item) {
|
||||
Date time = item.getStartTime();
|
||||
return DateTimeUtils.formatTime(time.getHours(), time.getMinutes());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTimeTo(BaseActivitySummary item) {
|
||||
Date time = item.getEndTime();
|
||||
return DateTimeUtils.formatTime(time.getHours(), time.getMinutes());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getActivityName(BaseActivitySummary item) {
|
||||
String activityLabel = item.getName();
|
||||
String separator = ",";
|
||||
if (activityLabel == null) {
|
||||
activityLabel = "";
|
||||
separator = "";
|
||||
}
|
||||
|
||||
String activityKindName = ActivityKind.fromCode(item.getActivityKind()).getLabel(getContext());
|
||||
return String.format("%s%s %s", activityKindName, separator, activityLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStepLabel(BaseActivitySummary item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDistanceLabel(BaseActivitySummary item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHrLabel(BaseActivitySummary item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getIntensityLabel(BaseActivitySummary item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDurationLabel(BaseActivitySummary item) {
|
||||
Long duration = item.getEndTime().getTime() - item.getStartTime().getTime();
|
||||
return DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSpeedLabel(BaseActivitySummary item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSessionCountLabel(BaseActivitySummary item) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasHR(BaseActivitySummary item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasIntensity(BaseActivitySummary item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasDistance(BaseActivitySummary item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasSteps(BaseActivitySummary item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasTotalSteps(BaseActivitySummary item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSummary(BaseActivitySummary item, int position) {
|
||||
return position == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEmptySession(BaseActivitySummary item, int position) { return false; }
|
||||
|
||||
@Override
|
||||
protected boolean isEmptySummary(BaseActivitySummary item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStepTotalLabel(BaseActivitySummary item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getIcon(BaseActivitySummary item) {
|
||||
return ActivityKind.fromCode(item.getActivityKind()).getIcon();
|
||||
}
|
||||
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
this.backgroundColor = backgroundColor;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,188 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
|
||||
|
||||
public class ActivityListItem {
|
||||
private final View rootView;
|
||||
private final TextView timeFromView;
|
||||
private final TextView timeToView;
|
||||
private final TextView activityName;
|
||||
private final TextView stepLabel;
|
||||
private final TextView distanceLabel;
|
||||
private final TextView hrLabel;
|
||||
private final TextView intensityLabel;
|
||||
private final TextView durationLabel;
|
||||
private final TextView dateLabel;
|
||||
private final LinearLayout timeLayout;
|
||||
private final LinearLayout hrLayout;
|
||||
private final LinearLayout stepsLayout;
|
||||
private final LinearLayout distanceLayout;
|
||||
private final LinearLayout intensityLayout;
|
||||
private final LinearLayout dateLayout;
|
||||
private final RelativeLayout parentLayout;
|
||||
private final ImageView activityIcon;
|
||||
private final ImageView gpsIcon;
|
||||
|
||||
private final int backgroundColor;
|
||||
private final int alternateColor;
|
||||
private final int selectedColor;
|
||||
|
||||
public ActivityListItem(final View itemView) {
|
||||
this.rootView = itemView;
|
||||
|
||||
this.timeFromView = itemView.findViewById(R.id.line_layout_time_from);
|
||||
this.timeToView = itemView.findViewById(R.id.line_layout_time_to);
|
||||
this.activityName = itemView.findViewById(R.id.line_layout_activity_name);
|
||||
this.stepLabel = itemView.findViewById(R.id.line_layout_step_label);
|
||||
this.distanceLabel = itemView.findViewById(R.id.line_layout_distance_label);
|
||||
this.hrLabel = itemView.findViewById(R.id.line_layout_hr_label);
|
||||
this.intensityLabel = itemView.findViewById(R.id.line_layout_intensity_label);
|
||||
this.durationLabel = itemView.findViewById(R.id.line_layout_duration_label);
|
||||
this.dateLabel = itemView.findViewById(R.id.line_layout_date_label);
|
||||
|
||||
this.timeLayout = itemView.findViewById(R.id.line_layout_time);
|
||||
this.hrLayout = itemView.findViewById(R.id.line_layout_hr);
|
||||
this.stepsLayout = itemView.findViewById(R.id.line_layout_step);
|
||||
this.distanceLayout = itemView.findViewById(R.id.line_layout_distance);
|
||||
this.intensityLayout = itemView.findViewById(R.id.line_layout_intensity);
|
||||
this.dateLayout = itemView.findViewById(R.id.line_layout_date);
|
||||
|
||||
this.parentLayout = itemView.findViewById(R.id.list_item_parent_layout);
|
||||
|
||||
this.activityIcon = itemView.findViewById(R.id.line_layout_activity_icon);
|
||||
this.gpsIcon = itemView.findViewById(R.id.line_layout_gps_icon);
|
||||
|
||||
this.backgroundColor = 0;
|
||||
this.alternateColor = getThemedColor(itemView.getContext(), R.attr.alternate_row_background);
|
||||
this.selectedColor = ContextCompat.getColor(itemView.getContext(), R.color.accent);
|
||||
}
|
||||
|
||||
public void update(@Nullable final Date timeFrom,
|
||||
@Nullable final Date timeTo,
|
||||
final ActivityKind activityKind,
|
||||
@Nullable final String activityLabel,
|
||||
final int steps,
|
||||
final float distance,
|
||||
final int heartRate,
|
||||
final float intensity,
|
||||
final long duration,
|
||||
final boolean hasGps,
|
||||
@Nullable final Date date,
|
||||
final boolean zebraStripe,
|
||||
final boolean selected) {
|
||||
final String activityKindLabel = activityKind.getLabel(activityName.getContext());
|
||||
if (StringUtils.isNotBlank(activityLabel)) {
|
||||
activityName.setText(String.format("%s, %s", activityKindLabel, activityLabel));
|
||||
} else {
|
||||
activityName.setText(activityKindLabel);
|
||||
}
|
||||
durationLabel.setText(DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS));
|
||||
|
||||
if (heartRate > 0) {
|
||||
hrLabel.setText(String.valueOf(heartRate));
|
||||
hrLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
hrLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (intensity >= 0) {
|
||||
final DecimalFormat df = new DecimalFormat("###");
|
||||
intensityLabel.setText(df.format(intensity));
|
||||
intensityLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
intensityLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (distance > 0) {
|
||||
distanceLabel.setText(FormatUtils.getFormattedDistanceLabel(distance));
|
||||
distanceLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
distanceLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (steps > 0) {
|
||||
stepLabel.setText(String.valueOf(steps));
|
||||
stepsLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
stepsLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (date != null) {
|
||||
dateLabel.setText(formatDate(date));
|
||||
dateLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
dateLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (timeFrom != null && timeTo != null) {
|
||||
timeFromView.setText(DateTimeUtils.formatTime(timeFrom.getHours(), timeFrom.getMinutes()));
|
||||
timeToView.setText(DateTimeUtils.formatTime(timeTo.getHours(), timeTo.getMinutes()));
|
||||
timeLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
timeLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (hasGps) {
|
||||
gpsIcon.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
gpsIcon.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
activityIcon.setImageResource(activityKind.getIcon());
|
||||
|
||||
if (parentLayout != null) {
|
||||
if (selected) {
|
||||
parentLayout.setBackgroundColor(selectedColor);
|
||||
} else if (zebraStripe) {
|
||||
parentLayout.setBackgroundColor(alternateColor);
|
||||
} else {
|
||||
parentLayout.setBackgroundColor(backgroundColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String formatDate(final Date date) {
|
||||
if (date != null) {
|
||||
final String activityDay;
|
||||
if (DateUtils.isToday(date.getTime())) {
|
||||
activityDay = rootView.getContext().getString(R.string.activity_summary_today);
|
||||
} else if (DateTimeUtils.isYesterday(date)) {
|
||||
activityDay = rootView.getContext().getString(R.string.activity_summary_yesterday);
|
||||
} else {
|
||||
activityDay = DateTimeUtils.formatDate(date, DateUtils.FORMAT_SHOW_WEEKDAY);
|
||||
}
|
||||
final String activityTime = DateTimeUtils.formatTime(date.getHours(), date.getMinutes());
|
||||
return String.format("%s, %s", activityDay, activityTime);
|
||||
}
|
||||
|
||||
return rootView.getContext().getString(R.string.unknown);
|
||||
}
|
||||
|
||||
public static int getThemedColor(Context context, int resid) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(resid, typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
}
|
@ -44,7 +44,8 @@ public class ActivitySummaryItems {
|
||||
}
|
||||
|
||||
public BaseActivitySummary getNextItem() {
|
||||
if (current_position + 1 < itemsAdapter.getCount()) {
|
||||
// last one is empty to avoid items behind fab
|
||||
if (current_position + 2 < itemsAdapter.getItemCount()) {
|
||||
current_position += 1;
|
||||
return itemsAdapter.getItem(current_position);
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
|
||||
|
||||
public final class ActivitySummaryUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryUtils.class);
|
||||
|
||||
private ActivitySummaryUtils() {
|
||||
// utility class
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getTrackFile(final BaseActivitySummary summary) {
|
||||
final String gpxTrack = summary.getGpxTrack();
|
||||
if (gpxTrack != null) {
|
||||
return FileUtils.tryFixPath(new File(gpxTrack));
|
||||
}
|
||||
final String rawDetails = summary.getRawDetailsPath();
|
||||
if (rawDetails != null && rawDetails.endsWith(".fit")) {
|
||||
return FileUtils.tryFixPath(new File(rawDetails));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getGpxFile(final BaseActivitySummary summary) {
|
||||
final File trackFile = getTrackFile(summary);
|
||||
if (trackFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (trackFile.getName().endsWith(".gpx")) {
|
||||
return trackFile;
|
||||
} else if (trackFile.getName().endsWith(".fit")) {
|
||||
return convertFitToGpx(summary, trackFile);
|
||||
} else {
|
||||
LOG.error("Unknown track format for {}", trackFile.getName());
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to get gpx track", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static File convertFitToGpx(final BaseActivitySummary summary, final File file) throws IOException, ActivityTrackExporter.GPXTrackEmptyException {
|
||||
final FitFile fitFile = FitFile.parseIncoming(file);
|
||||
final List<ActivityPoint> activityPoints = fitFile.getRecords().stream()
|
||||
.filter(r -> r instanceof FitRecord)
|
||||
.map(r -> ((FitRecord) r).toActivityPoint())
|
||||
.filter(ap -> ap.getLocation() != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final ActivityTrack activityTrack = new ActivityTrack();
|
||||
activityTrack.setName(summary.getName());
|
||||
activityTrack.addTrackPoints(activityPoints);
|
||||
|
||||
final File cacheDir = GBApplication.getContext().getCacheDir();
|
||||
final File rawCacheDir = new File(cacheDir, "gpx");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
rawCacheDir.mkdir();
|
||||
final File gpxFile = new File(rawCacheDir, file.getName().replace(".fit", ".gpx"));
|
||||
|
||||
final GPXExporter gpxExporter = new GPXExporter();
|
||||
gpxExporter.performExport(activityTrack, gpxFile);
|
||||
|
||||
return gpxFile;
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ListView
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/itemListView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
@ -12,16 +12,11 @@
|
||||
android:id="@+id/list_item_subparent_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="@drawable/list_selector"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
@ -37,7 +32,7 @@
|
||||
android:layout_gravity="end"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="14:30" />
|
||||
android:text="@string/time_empty_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/line_layout_time_to"
|
||||
@ -48,7 +43,7 @@
|
||||
android:gravity="bottom"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="16:30" />
|
||||
android:text="@string/time_empty_value" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -65,7 +60,7 @@
|
||||
android:layout_marginBottom="4dp"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
android:padding="8dp"
|
||||
app:srcCompat="@drawable/ic_activity_running" />
|
||||
app:srcCompat="@drawable/ic_activity_unknown" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -80,7 +75,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="Running"
|
||||
android:text="@string/unknown"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
@ -101,7 +96,7 @@
|
||||
android:gravity="end"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="25min" />
|
||||
android:text="@string/stats_empty_value" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/line_layout_gps_icon"
|
||||
@ -146,7 +141,7 @@
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15000" />
|
||||
android:text="@string/stats_empty_value" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -176,7 +171,7 @@
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="101" />
|
||||
android:text="@string/stats_empty_value" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -206,7 +201,7 @@
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15.1km" />
|
||||
android:text="@string/stats_empty_value" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -236,7 +231,7 @@
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="122" />
|
||||
android:text="@string/stats_empty_value" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -266,7 +261,7 @@
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="1.1.1973" />
|
||||
android:text="@string/stats_empty_value" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -277,6 +272,4 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -16,7 +16,7 @@
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<ListView
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/itemListView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -24,7 +24,7 @@
|
||||
android:layout_alignParentBottom="false"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_marginTop="0dp">
|
||||
</ListView>
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
|
@ -41,7 +41,7 @@
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15000"
|
||||
android:text="@string/stats_empty_value"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</LinearLayout>
|
@ -41,7 +41,7 @@
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15"
|
||||
android:text="@string/stats_empty_value"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</LinearLayout>
|
@ -41,7 +41,7 @@
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15.1km"
|
||||
android:text="@string/stats_empty_value"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</LinearLayout>
|
@ -41,7 +41,7 @@
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="122"
|
||||
android:text="@string/stats_empty_value"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</LinearLayout>
|
@ -41,7 +41,7 @@
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15000"
|
||||
android:text="@string/stats_empty_value"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</LinearLayout>
|
@ -1011,6 +1011,7 @@
|
||||
<string name="sleep_colored_stats_rem_avg">REM AVG</string>
|
||||
<string name="sleep_colored_stats_awake_avg">Awake AVG</string>
|
||||
<string name="stats_empty_value">-</string>
|
||||
<string name="time_empty_value" translatable="false">--:--</string>
|
||||
<string name="stats_lowest_hr">Lowest HR</string>
|
||||
<string name="stats_highest_hr">Highest HR</string>
|
||||
<string name="transition">Transition</string>
|
||||
@ -3264,6 +3265,7 @@
|
||||
<string name="folder_is_empty">Folder is empty</string>
|
||||
<string name="folder">Folder</string>
|
||||
<string name="url">URL</string>
|
||||
<string name="number_selected_items">%1d selected</string>
|
||||
<string name="garmin_agps_local_file">Local file</string>
|
||||
<string name="pref_garmin_agps_help">The list below contains all URLs requested by the watch for AGPS updates. You can select a file from the phone\'s storage that will be sent to the watch when it requests an update.</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
|
Loading…
Reference in New Issue
Block a user