mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Make workouts list async
This commit is contained in:
@@ -247,7 +247,7 @@
|
||||
android:label="@string/notifications_app_icon_upload"
|
||||
android:parentActivityName=".activities.devicesettings.DeviceSettingsActivity" />
|
||||
<activity
|
||||
android:name=".activities.ActivitySummariesActivity"
|
||||
android:name=".activities.workouts.WorkoutListActivity"
|
||||
android:label="@string/activity_summaries"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
@@ -663,12 +663,12 @@
|
||||
<activity
|
||||
android:name=".activities.workouts.WorkoutDetailsActivity"
|
||||
android:label="@string/activity_summary_detail"
|
||||
android:parentActivityName=".activities.ActivitySummariesActivity"
|
||||
android:parentActivityName=".activities.workouts.WorkoutListActivity"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".activities.ActivitySummariesFilter"
|
||||
android:label="@string/activity_filter_filter_title"
|
||||
android:parentActivityName=".activities.ActivitySummariesActivity"
|
||||
android:parentActivityName=".activities.workouts.WorkoutListActivity"
|
||||
android:excludeFromRecents="true"/>
|
||||
|
||||
<activity
|
||||
|
||||
-460
@@ -1,460 +0,0 @@
|
||||
/* Copyright (C) 2017-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
|
||||
Pfeiffer, Daniele Gobbetti, José Rebelo, Petr Vaněk
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutDetailsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
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;
|
||||
|
||||
|
||||
public class ActivitySummariesActivity extends AbstractListActivity<BaseActivitySummary> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesActivity.class);
|
||||
|
||||
static final int ACTIVITY_FILTER = 1;
|
||||
static final int ACTIVITY_DETAIL = 11;
|
||||
HashMap<String, ActivityKind> activityKindMap = new HashMap<>(0);
|
||||
int activityFilter = 0;
|
||||
long dateFromFilter = 0;
|
||||
long dateToFilter = 0;
|
||||
long deviceFilter;
|
||||
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) {
|
||||
if (!GBDevice.ACTION_DEVICE_CHANGED.equals(intent.getAction())) {
|
||||
LOG.warn("Got unexpected action {}", intent.getAction());
|
||||
return;
|
||||
}
|
||||
final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (device == null) {
|
||||
LOG.error("Got device changed without device");
|
||||
return;
|
||||
}
|
||||
if (!device.equals(mGBDevice)) {
|
||||
return;
|
||||
}
|
||||
if (device.isBusy()) {
|
||||
swipeLayout.setRefreshing(true);
|
||||
} else {
|
||||
boolean wasBusy = swipeLayout.isRefreshing();
|
||||
swipeLayout.setRefreshing(false);
|
||||
if (wasBusy) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
getMenuInflater().inflate(R.menu.activity_list_menu, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
final int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
// back button, close drawer if open, otherwise exit
|
||||
finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.activity_action_manage_timestamp) {
|
||||
resetFetchTimestampToChosenDate();
|
||||
return true;
|
||||
} else if (itemId == R.id.activity_action_filter) {
|
||||
runFilterActivity();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
if (requestCode == ACTIVITY_FILTER && resultData != null && resultData.getExtras() != null) {
|
||||
Bundle bundle = resultData.getExtras();
|
||||
activityFilter = bundle.getInt("activityFilter", 0);
|
||||
dateFromFilter = bundle.getLong("dateFromFilter", 0);
|
||||
dateToFilter = bundle.getLong("dateToFilter", 0);
|
||||
deviceFilter = bundle.getLong("deviceFilter", 0);
|
||||
nameContainsFilter = bundle.getString("nameContainsFilter");
|
||||
//noinspection unchecked
|
||||
itemsFilter = (List<Long>) bundle.getSerializable("itemsFilter");
|
||||
setActivityKindFilter(activityFilter);
|
||||
setDateFromFilter(dateFromFilter);
|
||||
setDateToFilter(dateToFilter);
|
||||
setNameContainsFilter(nameContainsFilter);
|
||||
setItemsFilter(itemsFilter);
|
||||
setDeviceFilter(deviceFilter);
|
||||
refresh();
|
||||
}
|
||||
if (requestCode == ACTIVITY_DETAIL) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Must provide a device when invoking this activity");
|
||||
}
|
||||
deviceFilter = getDeviceId(mGBDevice);
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
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
|
||||
ActivitySummary summary = activitySummariesAdapter.getItem(position);
|
||||
if (summary != null) {
|
||||
try {
|
||||
showActivityDetail(position - 1);
|
||||
} catch (Exception e) {
|
||||
GB.toast(getApplicationContext(), "Unable to display Activity Detail, maybe the activity is not available yet: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
activitySummariesAdapter.setOnItemLongClickListener(position -> {
|
||||
selectedItems.set(position, !selectedItems.get(position));
|
||||
activitySummariesAdapter.notifyItemChanged(position);
|
||||
|
||||
if (!selectedItems.isEmpty()) {
|
||||
startActionMode();
|
||||
} else {
|
||||
stopActionMode();
|
||||
}
|
||||
});
|
||||
setItemAdapter(activitySummariesAdapter);
|
||||
|
||||
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(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(final ActionMode mode, final Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
|
||||
boolean processed = false;
|
||||
final int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.activity_action_delete) {
|
||||
final List<BaseActivitySummary> toDelete = new ArrayList<>();
|
||||
for (int i = 0; i < selectedItems.length(); i++) {
|
||||
if (selectedItems.get(i)) {
|
||||
toDelete.add(getItemAdapter().getItem(i));
|
||||
}
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(ActivitySummariesActivity.this)
|
||||
.setTitle(ActivitySummariesActivity.this.getString(R.string.sports_activity_confirm_delete_title, toDelete.size()))
|
||||
.setMessage(ActivitySummariesActivity.this.getString(R.string.sports_activity_confirm_delete_description, toDelete.size()))
|
||||
.setIcon(R.drawable.ic_delete_forever)
|
||||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> deleteItems(toDelete))
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show();
|
||||
|
||||
processed = true;
|
||||
} else if (itemId == R.id.activity_action_export) {
|
||||
final List<String> paths = new ArrayList<>();
|
||||
|
||||
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.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shareMultiple(paths);
|
||||
processed = true;
|
||||
} else if (itemId == R.id.activity_action_select_all) {
|
||||
for (int i = 1; i < getItemAdapter().getItemCount() - 1; i++) {
|
||||
if (!selectedItems.get(i)) {
|
||||
numSelected[0]++;
|
||||
selectedItems.set(i, true);
|
||||
getItemAdapter().notifyItemChanged(i);
|
||||
}
|
||||
}
|
||||
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 < 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsFilter = toFilter;
|
||||
setItemsFilter(itemsFilter);
|
||||
refresh();
|
||||
|
||||
processed = true;
|
||||
}
|
||||
actionMode.finish();
|
||||
mActionMode = null;
|
||||
return processed;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, ActivityKind> fillKindMap() {
|
||||
LinkedHashMap<String, ActivityKind> newMap = new LinkedHashMap<>(0); //reset
|
||||
|
||||
newMap.put(getString(R.string.activity_summaries_all_activities), ActivityKind.UNKNOWN);
|
||||
for (BaseActivitySummary item : getItemAdapter().getItems()) {
|
||||
String activityName = ActivityKind.fromCode(item.getActivityKind()).getLabel(this);
|
||||
if (!newMap.containsKey(activityName) && item.getActivityKind() != 0) {
|
||||
newMap.put(activityName, ActivityKind.fromCode(item.getActivityKind()));
|
||||
}
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
|
||||
public void resetFetchTimestampToChosenDate() {
|
||||
final Calendar currentDate = Calendar.getInstance();
|
||||
new DatePickerDialog(this, (view, year, monthOfYear, dayOfMonth) -> {
|
||||
Calendar date = Calendar.getInstance();
|
||||
date.set(year, monthOfYear, dayOfMonth);
|
||||
|
||||
long timestamp = date.getTimeInMillis() - 1000;
|
||||
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress()).edit();
|
||||
editor.remove("lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
|
||||
editor.putLong("lastSportsActivityTimeMillis", timestamp);
|
||||
editor.apply();
|
||||
}, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void deleteItems(List<BaseActivitySummary> items) {
|
||||
for (BaseActivitySummary item : items) {
|
||||
try {
|
||||
item.delete();
|
||||
} catch (Exception e) {
|
||||
//pass delete error
|
||||
}
|
||||
}
|
||||
// Adapter is fully reloaded after refresh
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void fetchTrackData() {
|
||||
if (mGBDevice.isInitialized() && !mGBDevice.isBusy()) {
|
||||
swipeLayout.setRefreshing(true);
|
||||
GBApplication.deviceService(mGBDevice).onFetchRecordedData(RecordedDataTypes.TYPE_GPS_TRACKS);
|
||||
} else {
|
||||
swipeLayout.setRefreshing(false);
|
||||
if (!mGBDevice.isInitialized()) {
|
||||
GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void shareMultiple(List<String> paths) {
|
||||
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
for (String path : paths) {
|
||||
File file = new File(path);
|
||||
uris.add(FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".screenshot_provider", file));
|
||||
}
|
||||
|
||||
if (!uris.isEmpty()) {
|
||||
final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
intent.setType("application/gpx+xml");
|
||||
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
|
||||
startActivity(Intent.createChooser(intent, "SHARE"));
|
||||
} else {
|
||||
GB.toast(this, "No selected activity contains a GPX track to share", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void showActivityDetail(final int position) {
|
||||
final Intent intent = WorkoutDetailsActivity.Companion.createFilteredListIntent(
|
||||
this,
|
||||
mGBDevice,
|
||||
position,
|
||||
activityFilter,
|
||||
dateFromFilter,
|
||||
dateToFilter,
|
||||
nameContainsFilter,
|
||||
deviceFilter,
|
||||
itemsFilter
|
||||
);
|
||||
|
||||
startActivityForResult(intent, ACTIVITY_DETAIL);
|
||||
}
|
||||
|
||||
private void runFilterActivity() {
|
||||
Intent filterIntent = new Intent(this, ActivitySummariesFilter.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putSerializable("activityKindMap", activityKindMap);
|
||||
bundle.putSerializable("itemsFilter", (Serializable) itemsFilter);
|
||||
bundle.putInt("activityFilter", activityFilter);
|
||||
bundle.putLong("dateFromFilter", dateFromFilter);
|
||||
bundle.putLong("dateToFilter", dateToFilter);
|
||||
bundle.putLong("deviceFilter", deviceFilter);
|
||||
bundle.putLong("initial_deviceFilter", getDeviceId(mGBDevice));
|
||||
bundle.putString("nameContainsFilter", nameContainsFilter);
|
||||
filterIntent.putExtras(bundle);
|
||||
startActivityForResult(filterIntent, ACTIVITY_FILTER);
|
||||
}
|
||||
|
||||
private long getDeviceId(final GBDevice device) {
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final Device dbDevice = DBHelper.findDevice(device, handler.getDaoSession());
|
||||
return Objects.requireNonNull(dbDevice).getId();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get device id", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+469
@@ -0,0 +1,469 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.workouts
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Bundle
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractListActivity
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesFilter
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.WorkoutSummariesAdapter
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ActivitySummaryUtils
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.util.BitSet
|
||||
import java.util.Calendar
|
||||
|
||||
class WorkoutListActivity : AbstractListActivity<BaseActivitySummary>() {
|
||||
private val viewModel: WorkoutListViewModel by viewModels()
|
||||
|
||||
private var activityKindMap = HashMap<String, ActivityKind>(0)
|
||||
private var activityFilter = 0
|
||||
private var dateFromFilter = 0L
|
||||
private var dateToFilter = 0L
|
||||
private var deviceFilter = 0L
|
||||
private var itemsFilter: List<Long>? = null
|
||||
private var nameContainsFilter: String? = null
|
||||
private var gbDevice: GBDevice? = null
|
||||
private var selectedItems: BitSet? = null
|
||||
private lateinit var swipeLayout: SwipeRefreshLayout
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != GBDevice.ACTION_DEVICE_CHANGED) {
|
||||
LOG.warn("Got unexpected action {}", intent.action)
|
||||
return
|
||||
}
|
||||
val device = intent.getParcelableExtra<GBDevice>(GBDevice.EXTRA_DEVICE)
|
||||
if (device == null) {
|
||||
LOG.error("Got device changed without device")
|
||||
return
|
||||
}
|
||||
if (device != gbDevice) {
|
||||
return
|
||||
}
|
||||
if (device.isBusy) {
|
||||
swipeLayout.isRefreshing = true
|
||||
} else {
|
||||
val wasBusy = swipeLayout.isRefreshing
|
||||
swipeLayout.isRefreshing = false
|
||||
if (wasBusy) {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.activity_list_menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
R.id.activity_action_manage_timestamp -> {
|
||||
resetFetchTimestampToChosenDate()
|
||||
true
|
||||
}
|
||||
R.id.activity_action_filter -> {
|
||||
runFilterActivity()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData)
|
||||
resultData?.extras?.let { bundle ->
|
||||
when (requestCode) {
|
||||
ACTIVITY_FILTER -> {
|
||||
activityFilter = bundle.getInt("activityFilter", 0)
|
||||
dateFromFilter = bundle.getLong("dateFromFilter", 0)
|
||||
dateToFilter = bundle.getLong("dateToFilter", 0)
|
||||
deviceFilter = bundle.getLong("deviceFilter", 0)
|
||||
nameContainsFilter = bundle.getString("nameContainsFilter")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
itemsFilter = bundle.getSerializable("itemsFilter") as? List<Long>
|
||||
refresh()
|
||||
}
|
||||
ACTIVITY_DETAIL -> refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
gbDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE)
|
||||
?: throw IllegalArgumentException("Must provide a device when invoking this activity")
|
||||
deviceFilter = getDeviceId(gbDevice!!)
|
||||
|
||||
val filterLocal = IntentFilter(GBDevice.ACTION_DEVICE_CHANGED)
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filterLocal)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setupViews()
|
||||
setupViewModel()
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
val workoutSummariesAdapter = WorkoutSummariesAdapter(
|
||||
this,
|
||||
gbDevice!!,
|
||||
activityFilter,
|
||||
dateFromFilter,
|
||||
dateToFilter,
|
||||
nameContainsFilter,
|
||||
deviceFilter,
|
||||
itemsFilter
|
||||
)
|
||||
selectedItems = workoutSummariesAdapter.selectedItems
|
||||
|
||||
workoutSummariesAdapter.setOnItemClickListener { position ->
|
||||
if (!selectedItems!!.isEmpty) {
|
||||
selectedItems!!.set(position, !selectedItems!!.get(position))
|
||||
workoutSummariesAdapter.notifyItemChanged(position)
|
||||
if (!selectedItems!!.isEmpty) {
|
||||
startActionMode()
|
||||
} else {
|
||||
stopActionMode()
|
||||
}
|
||||
return@setOnItemClickListener
|
||||
}
|
||||
if (position == 0) return@setOnItemClickListener // item 0 is empty for dashboard
|
||||
val summary = workoutSummariesAdapter.getItem(position)
|
||||
if (summary != null) {
|
||||
try {
|
||||
showActivityDetail(position - 1)
|
||||
} catch (e: Exception) {
|
||||
GB.toast(
|
||||
applicationContext,
|
||||
"Unable to display Activity Detail: ${e.localizedMessage}",
|
||||
Toast.LENGTH_LONG,
|
||||
GB.ERROR,
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workoutSummariesAdapter.setOnItemLongClickListener { position ->
|
||||
selectedItems!!.set(position, !selectedItems!!.get(position))
|
||||
workoutSummariesAdapter.notifyItemChanged(position)
|
||||
|
||||
if (!selectedItems!!.isEmpty) {
|
||||
startActionMode()
|
||||
} else {
|
||||
stopActionMode()
|
||||
}
|
||||
}
|
||||
|
||||
setItemAdapter(workoutSummariesAdapter)
|
||||
|
||||
swipeLayout = findViewById(R.id.list_activity_swipe_layout)
|
||||
swipeLayout.setOnRefreshListener { fetchTrackData() }
|
||||
|
||||
val fab: FloatingActionButton = findViewById(R.id.fab)
|
||||
fab.setOnClickListener { fetchTrackData() }
|
||||
}
|
||||
|
||||
private fun setupViewModel() {
|
||||
viewModel.summaries.observe(this) { summaries ->
|
||||
itemAdapter?.setItems(summaries, true)
|
||||
activityKindMap = fillKindMap()
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
swipeLayout.isRefreshing = isLoading
|
||||
}
|
||||
|
||||
viewModel.error.observe(this) { error ->
|
||||
error?.let {
|
||||
GB.toast(this, it, Toast.LENGTH_LONG, GB.ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isDashboardLoading.observe(this) { isLoading ->
|
||||
(itemAdapter as WorkoutSummariesAdapter).isDashboardLoading = isLoading
|
||||
itemAdapter?.notifyItemChanged(0)
|
||||
}
|
||||
|
||||
viewModel.dashboardStats.observe(this) { stats ->
|
||||
(itemAdapter as WorkoutSummariesAdapter).dashboardStats = stats
|
||||
itemAdapter?.notifyItemChanged(0)
|
||||
}
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun stopActionMode() {
|
||||
actionMode?.finish()
|
||||
actionMode = null
|
||||
}
|
||||
|
||||
private fun startActionMode() {
|
||||
val numSelected = selectedItems!!.cardinality()
|
||||
|
||||
if (actionMode != null) {
|
||||
// already in action mode
|
||||
actionMode?.title = getString(R.string.number_selected_items, numSelected)
|
||||
return
|
||||
}
|
||||
|
||||
actionMode = startActionMode(object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.title = getString(R.string.number_selected_items, numSelected)
|
||||
menuInflater.inflate(R.menu.activity_list_context_menu, menu)
|
||||
findViewById<View>(R.id.fab).visibility = View.INVISIBLE
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu) = false
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.activity_action_delete -> {
|
||||
val toDelete = ArrayList<BaseActivitySummary>()
|
||||
for (i in 0 until selectedItems!!.length()) {
|
||||
if (selectedItems!!.get(i)) {
|
||||
itemAdapter?.getItem(i)?.let { toDelete.add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(this@WorkoutListActivity)
|
||||
.setTitle(getString(R.string.sports_activity_confirm_delete_title, toDelete.size))
|
||||
.setMessage(getString(R.string.sports_activity_confirm_delete_description, toDelete.size))
|
||||
.setIcon(R.drawable.ic_delete_forever)
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> deleteItems(toDelete) }
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show()
|
||||
|
||||
return true
|
||||
}
|
||||
R.id.activity_action_export -> {
|
||||
val paths = ArrayList<String>()
|
||||
for (i in 0 until selectedItems!!.length()) {
|
||||
if (selectedItems!!.get(i)) {
|
||||
itemAdapter?.getItem(i)?.let { summary ->
|
||||
ActivitySummaryUtils.getGpxFile(summary)?.let { file ->
|
||||
paths.add(file.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shareMultiple(paths)
|
||||
return true
|
||||
}
|
||||
R.id.activity_action_select_all -> {
|
||||
for (i in 1 until itemAdapter!!.itemCount - 1) {
|
||||
if (!selectedItems!!.get(i)) {
|
||||
selectedItems!!.set(i, true)
|
||||
itemAdapter!!.notifyItemChanged(i)
|
||||
}
|
||||
}
|
||||
mode.title = getString(R.string.number_selected_items, selectedItems!!.cardinality())
|
||||
return true
|
||||
}
|
||||
R.id.activity_action_addto_filter -> {
|
||||
val toFilter = ArrayList<Long>()
|
||||
for (i in 0 until selectedItems!!.length()) {
|
||||
if (selectedItems!!.get(i)) {
|
||||
itemAdapter?.getItem(i)?.id?.let { id ->
|
||||
toFilter.add(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsFilter = toFilter
|
||||
refresh()
|
||||
return true
|
||||
}
|
||||
}
|
||||
mode.finish()
|
||||
actionMode = null
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
actionMode = null
|
||||
for (i in 0 until selectedItems!!.length()) {
|
||||
if (selectedItems!!.get(i)) {
|
||||
selectedItems!!.set(i, false)
|
||||
itemAdapter?.notifyItemChanged(i)
|
||||
}
|
||||
}
|
||||
findViewById<View>(R.id.fab).visibility = View.VISIBLE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun fillKindMap(): HashMap<String, ActivityKind> {
|
||||
val newMap = HashMap<String, ActivityKind>(0)
|
||||
newMap[getString(R.string.activity_summaries_all_activities)] = ActivityKind.UNKNOWN
|
||||
|
||||
itemAdapter?.items?.forEach { item ->
|
||||
val activityName = ActivityKind.fromCode(item.activityKind).getLabel(this)
|
||||
if (!newMap.containsKey(activityName) && item.activityKind != 0) {
|
||||
newMap[activityName] = ActivityKind.fromCode(item.activityKind)
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
private fun resetFetchTimestampToChosenDate() {
|
||||
val currentDate = Calendar.getInstance()
|
||||
DatePickerDialog(
|
||||
this,
|
||||
{ _, year, monthOfYear, dayOfMonth ->
|
||||
val date = Calendar.getInstance().apply {
|
||||
set(year, monthOfYear, dayOfMonth)
|
||||
}
|
||||
val timestamp = date.timeInMillis - 1000
|
||||
GBApplication.getDeviceSpecificSharedPrefs(gbDevice!!.address).edit().apply {
|
||||
remove("lastSportsActivityTimeMillis")
|
||||
putLong("lastSportsActivityTimeMillis", timestamp)
|
||||
apply()
|
||||
}
|
||||
},
|
||||
currentDate.get(Calendar.YEAR),
|
||||
currentDate.get(Calendar.MONTH),
|
||||
currentDate.get(Calendar.DATE)
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun deleteItems(items: List<BaseActivitySummary>) {
|
||||
items.forEach { item ->
|
||||
try {
|
||||
item.delete()
|
||||
} catch (e: Exception) {
|
||||
// pass delete error
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun fetchTrackData() {
|
||||
gbDevice?.let { device ->
|
||||
if (device.isInitialized && !device.isBusy) {
|
||||
swipeLayout.isRefreshing = true
|
||||
GBApplication.deviceService(device).onFetchRecordedData(RecordedDataTypes.TYPE_GPS_TRACKS)
|
||||
} else {
|
||||
swipeLayout.isRefreshing = false
|
||||
if (!device.isInitialized) {
|
||||
GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareMultiple(paths: List<String>) {
|
||||
val uris = paths.map { path ->
|
||||
val file = File(path)
|
||||
FileProvider.getUriForFile(
|
||||
this,
|
||||
"${applicationContext.packageName}.screenshot_provider",
|
||||
file
|
||||
)
|
||||
}
|
||||
|
||||
if (uris.isNotEmpty()) {
|
||||
val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
|
||||
type = "application/gpx+xml"
|
||||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
|
||||
}
|
||||
startActivity(Intent.createChooser(intent, "SHARE"))
|
||||
} else {
|
||||
GB.toast(this, "No selected activity contains a GPX track to share", Toast.LENGTH_SHORT, GB.ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showActivityDetail(position: Int) {
|
||||
val intent = WorkoutDetailsActivity.createFilteredListIntent(
|
||||
this,
|
||||
gbDevice!!,
|
||||
position,
|
||||
activityFilter,
|
||||
dateFromFilter,
|
||||
dateToFilter,
|
||||
nameContainsFilter,
|
||||
deviceFilter,
|
||||
itemsFilter
|
||||
)
|
||||
startActivityForResult(intent, ACTIVITY_DETAIL)
|
||||
}
|
||||
|
||||
private fun runFilterActivity() {
|
||||
val filterIntent = Intent(this, ActivitySummariesFilter::class.java).apply {
|
||||
putExtra("activityKindMap", activityKindMap)
|
||||
putExtra("itemsFilter", ArrayList(itemsFilter ?: emptyList()))
|
||||
putExtra("activityFilter", activityFilter)
|
||||
putExtra("dateFromFilter", dateFromFilter)
|
||||
putExtra("dateToFilter", dateToFilter)
|
||||
putExtra("deviceFilter", deviceFilter)
|
||||
putExtra("initial_deviceFilter", getDeviceId(gbDevice!!))
|
||||
putExtra("nameContainsFilter", nameContainsFilter)
|
||||
}
|
||||
startActivityForResult(filterIntent, ACTIVITY_FILTER)
|
||||
}
|
||||
|
||||
private fun getDeviceId(device: GBDevice): Long {
|
||||
return try {
|
||||
GBApplication.acquireDB().use { handler ->
|
||||
DBHelper.findDevice(device, handler.daoSession)?.id ?: 0L
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Failed to get device id", e)
|
||||
0L
|
||||
}
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
gbDevice?.let { device ->
|
||||
viewModel.loadSummaries(
|
||||
device,
|
||||
activityFilter,
|
||||
dateFromFilter,
|
||||
dateToFilter,
|
||||
nameContainsFilter,
|
||||
deviceFilter,
|
||||
itemsFilter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(WorkoutListActivity::class.java)
|
||||
const val ACTIVITY_FILTER = 1
|
||||
const val ACTIVITY_DETAIL = 11
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.workouts
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.Date
|
||||
|
||||
|
||||
class WorkoutListViewModel : ViewModel() {
|
||||
private val _summaries = MutableLiveData<List<BaseActivitySummary>>()
|
||||
val summaries: LiveData<List<BaseActivitySummary>> = _summaries
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
private val _error = MutableLiveData<String?>()
|
||||
val error: LiveData<String?> = _error
|
||||
|
||||
private val _dashboardStats = MutableLiveData<DashboardStats?>()
|
||||
val dashboardStats: LiveData<DashboardStats?> = _dashboardStats
|
||||
|
||||
private val _isDashboardLoading = MutableLiveData<Boolean>()
|
||||
val isDashboardLoading: LiveData<Boolean> = _isDashboardLoading
|
||||
|
||||
fun loadSummaries(
|
||||
gbDevice: GBDevice,
|
||||
activityKindFilter: Int,
|
||||
dateFromFilter: Long,
|
||||
dateToFilter: Long,
|
||||
nameContainsFilter: String?,
|
||||
deviceFilter: Long,
|
||||
itemsFilter: List<Long>?
|
||||
) {
|
||||
_isLoading.value = true
|
||||
_error.value = null
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val summaries = withContext(Dispatchers.IO) {
|
||||
loadSummariesFromDatabase(
|
||||
gbDevice,
|
||||
activityKindFilter,
|
||||
dateFromFilter,
|
||||
dateToFilter,
|
||||
nameContainsFilter,
|
||||
deviceFilter,
|
||||
itemsFilter
|
||||
)
|
||||
}
|
||||
|
||||
val allSummaries: MutableList<BaseActivitySummary> = mutableListOf()
|
||||
|
||||
val dashboardSummary = BaseActivitySummary()
|
||||
allSummaries.add(dashboardSummary) // dashboard
|
||||
allSummaries.addAll(summaries)
|
||||
allSummaries.add(BaseActivitySummary()) // empty
|
||||
|
||||
_summaries.value = allSummaries
|
||||
|
||||
loadDashboardStats(gbDevice, summaries)
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Error loading summaries", e)
|
||||
_error.value = "Error loading summaries: ${e.localizedMessage}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDashboardStats(gbDevice: GBDevice, summaries: List<BaseActivitySummary>) {
|
||||
_isDashboardLoading.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val stats = withContext(Dispatchers.Default) {
|
||||
val parser = gbDevice.deviceCoordinator.getActivitySummaryParser(gbDevice, GBApplication.app())
|
||||
DashboardStats.from(parser, summaries)
|
||||
}
|
||||
_dashboardStats.value = stats
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Error loading dashboard stats", e)
|
||||
_error.value = "Error loading dashboard stats: ${e.localizedMessage}"
|
||||
} finally {
|
||||
_isDashboardLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadSummariesFromDatabase(
|
||||
gbDevice: GBDevice,
|
||||
activityKindFilter: Int,
|
||||
dateFromFilter: Long,
|
||||
dateToFilter: Long,
|
||||
nameContainsFilter: String?,
|
||||
deviceFilter: Long,
|
||||
itemsFilter: List<Long>?
|
||||
): List<BaseActivitySummary> {
|
||||
return GBApplication.acquireDB().use { dbHandler ->
|
||||
val summaryDao = dbHandler.daoSession.baseActivitySummaryDao
|
||||
val dbDevice = DBHelper.findDevice(gbDevice, dbHandler.daoSession)
|
||||
|
||||
val queryBuilder = summaryDao.queryBuilder()
|
||||
|
||||
// Apply device filter
|
||||
when {
|
||||
deviceFilter != 0L && deviceFilter != ALL_DEVICES -> {
|
||||
queryBuilder.where(BaseActivitySummaryDao.Properties.DeviceId.eq(deviceFilter))
|
||||
}
|
||||
dbDevice != null -> {
|
||||
queryBuilder.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.id))
|
||||
}
|
||||
}
|
||||
queryBuilder.orderDesc(BaseActivitySummaryDao.Properties.StartTime)
|
||||
|
||||
if (activityKindFilter != 0) {
|
||||
queryBuilder.where(BaseActivitySummaryDao.Properties.ActivityKind.eq(activityKindFilter))
|
||||
}
|
||||
|
||||
if (dateFromFilter != 0L) {
|
||||
queryBuilder.where(BaseActivitySummaryDao.Properties.StartTime.gt(Date(dateFromFilter)))
|
||||
}
|
||||
if (dateToFilter != 0L) {
|
||||
queryBuilder.where(BaseActivitySummaryDao.Properties.EndTime.lt(Date(dateToFilter)))
|
||||
}
|
||||
|
||||
if (!nameContainsFilter.isNullOrEmpty()) {
|
||||
queryBuilder.where(BaseActivitySummaryDao.Properties.Name.like("%${nameContainsFilter}%"))
|
||||
}
|
||||
|
||||
if (!itemsFilter.isNullOrEmpty()) {
|
||||
queryBuilder.where(BaseActivitySummaryDao.Properties.Id.`in`(itemsFilter))
|
||||
}
|
||||
|
||||
queryBuilder.list()
|
||||
}
|
||||
}
|
||||
|
||||
data class DashboardStats(
|
||||
val durationSum: Long,
|
||||
val caloriesBurntSum: Double,
|
||||
val distanceSum: Double,
|
||||
val activeSecondsSum: Long,
|
||||
val firstItemDate: Long,
|
||||
val lastItemDate: Long,
|
||||
val activityIcon: Int
|
||||
) {
|
||||
companion object {
|
||||
fun from(summaryParser: ActivitySummaryParser, activities: List<BaseActivitySummary>): DashboardStats {
|
||||
var durationSum = 0L
|
||||
var caloriesBurntSum = 0.0
|
||||
var distanceSum = 0.0
|
||||
var activeSecondsSum = 0L
|
||||
var firstItemDate = 0L
|
||||
var lastItemDate = 0L
|
||||
var activityIcon = 0
|
||||
var activitySame = true
|
||||
|
||||
for (summary in activities) {
|
||||
if (summary.startTime == null) continue // first item is empty, for dashboard
|
||||
|
||||
if (firstItemDate == 0L) firstItemDate = summary.startTime.time
|
||||
lastItemDate = summary.endTime.time
|
||||
durationSum += (summary.endTime.time - summary.startTime.time)
|
||||
|
||||
if (activityIcon == 0) {
|
||||
activityIcon = summary.activityKind
|
||||
} else if (activityIcon != summary.activityKind) {
|
||||
activitySame = false
|
||||
}
|
||||
|
||||
val activitySummaryJsonSummary = ActivitySummaryJsonSummary(summaryParser, summary)
|
||||
val summarySubData = activitySummaryJsonSummary.getSummaryData(false)
|
||||
|
||||
if (summarySubData != null) {
|
||||
if (summarySubData.has("caloriesBurnt")) {
|
||||
caloriesBurntSum += summarySubData.getNumber("caloriesBurnt", 0).toDouble()
|
||||
}
|
||||
if (summarySubData.has("distanceMeters")) {
|
||||
distanceSum += summarySubData.getNumber("distanceMeters", 0).toDouble()
|
||||
}
|
||||
if (summarySubData.has("activeSeconds")) {
|
||||
activeSecondsSum += summarySubData.getNumber("activeSeconds", 0).toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DashboardStats(
|
||||
durationSum = durationSum,
|
||||
caloriesBurntSum = caloriesBurntSum,
|
||||
distanceSum = distanceSum,
|
||||
activeSecondsSum = activeSecondsSum,
|
||||
firstItemDate = firstItemDate,
|
||||
lastItemDate = lastItemDate,
|
||||
activityIcon = if (activitySame) activityIcon else 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(WorkoutListViewModel::class.java)
|
||||
const val ALL_DEVICES = 999L
|
||||
}
|
||||
}
|
||||
-394
@@ -1,394 +0,0 @@
|
||||
/* Copyright (C) 2017-2024 Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti,
|
||||
José Rebelo, Petr Vaněk
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
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;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
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;
|
||||
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);
|
||||
private final GBDevice device;
|
||||
long dateFromFilter;
|
||||
long dateToFilter;
|
||||
long deviceFilter;
|
||||
String nameContainsFilter;
|
||||
List<Long> itemsFilter;
|
||||
private int activityKindFilter;
|
||||
|
||||
public ActivitySummariesAdapter(Context context, GBDevice device, int activityKindFilter, long dateFromFilter, long dateToFilter, String nameContainsFilter, long deviceFilter, List<Long> itemsFilter) {
|
||||
super(context);
|
||||
this.device = device;
|
||||
this.activityKindFilter = activityKindFilter;
|
||||
this.dateFromFilter = dateFromFilter;
|
||||
this.dateToFilter = dateToFilter;
|
||||
this.nameContainsFilter = nameContainsFilter;
|
||||
this.deviceFilter = deviceFilter;
|
||||
this.itemsFilter = itemsFilter;
|
||||
loadItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadItems() {
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
BaseActivitySummaryDao summaryDao = handler.getDaoSession().getBaseActivitySummaryDao();
|
||||
Device dbDevice = DBHelper.findDevice(device, handler.getDaoSession());
|
||||
assert dbDevice != null;
|
||||
|
||||
QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
|
||||
|
||||
if (deviceFilter == ALL_DEVICES) {
|
||||
qb.orderDesc(BaseActivitySummaryDao.Properties.StartTime);
|
||||
} else if (deviceFilter != 0) {
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.DeviceId.eq(
|
||||
deviceFilter)).orderDesc(BaseActivitySummaryDao.Properties.StartTime);
|
||||
} else {
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.DeviceId.eq(
|
||||
dbDevice.getId())).orderDesc(BaseActivitySummaryDao.Properties.StartTime);
|
||||
}
|
||||
|
||||
if (activityKindFilter != 0) {
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.ActivityKind.eq(activityKindFilter));
|
||||
}
|
||||
|
||||
if (dateFromFilter != 0) {
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.StartTime.gt(new Date(dateFromFilter)));
|
||||
}
|
||||
if (dateToFilter != 0) {
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.EndTime.lt(new Date(dateToFilter)));
|
||||
}
|
||||
if (nameContainsFilter != null && !nameContainsFilter.isEmpty()) {
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.Name.like("%" + nameContainsFilter + "%"));
|
||||
}
|
||||
if (itemsFilter != null) {
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.Id.in(itemsFilter));
|
||||
}
|
||||
|
||||
|
||||
List<BaseActivitySummary> allSummaries = new ArrayList<>();
|
||||
// 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) {
|
||||
return switch (viewType) {
|
||||
case 0 -> // dashboard
|
||||
new DashboardViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_summary_dashboard_item, parent, false));
|
||||
case 2 -> // item
|
||||
new ActivityItemViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_item, parent, false));
|
||||
default -> super.onCreateViewHolder(parent, viewType);
|
||||
};
|
||||
}
|
||||
|
||||
public void setActivityKindFilter(int filter) {
|
||||
this.activityKindFilter = filter;
|
||||
}
|
||||
|
||||
public void setDateFromFilter(long date) {
|
||||
this.dateFromFilter = date;
|
||||
}
|
||||
|
||||
public void setDateToFilter(long date) {
|
||||
this.dateToFilter = date;
|
||||
}
|
||||
|
||||
public void setNameContainsFilter(String name) {
|
||||
this.nameContainsFilter = name;
|
||||
}
|
||||
|
||||
public void setItemsFilter(List<Long> items) {
|
||||
this.itemsFilter = items;
|
||||
}
|
||||
|
||||
public void setDeviceFilter(long device) {
|
||||
this.deviceFilter = device;
|
||||
}
|
||||
|
||||
public int getActivityKindFilter() {
|
||||
return this.activityKindFilter;
|
||||
}
|
||||
|
||||
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.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 (getActivityKindFilter() != 0) {
|
||||
ActivityKind activityKind = ActivityKind.fromCode(getActivityKindFilter());
|
||||
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());
|
||||
} else {
|
||||
activityIconView.setImageResource(R.drawable.ic_activity_unknown_small);
|
||||
activityIconBigView.setImageResource(R.drawable.ic_activity_unknown_small);
|
||||
}
|
||||
|
||||
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 activityIcon = 0;
|
||||
boolean activitySame = true;
|
||||
|
||||
for (BaseActivitySummary summary : activities) {
|
||||
if (summary.getStartTime() == null) continue; //first item is empty, for dashboard
|
||||
|
||||
if (firstItemDate == 0) firstItemDate = summary.getStartTime().getTime();
|
||||
lastItemDate = summary.getEndTime().getTime();
|
||||
durationSum += summary.getEndTime().getTime() - summary.getStartTime().getTime();
|
||||
|
||||
if (activityIcon == 0) {
|
||||
activityIcon = summary.getActivityKind();
|
||||
} else {
|
||||
if (activityIcon != summary.getActivityKind()) {
|
||||
activitySame = false;
|
||||
}
|
||||
}
|
||||
|
||||
final ActivitySummaryJsonSummary activitySummaryJsonSummary = new ActivitySummaryJsonSummary(summaryParser, summary);
|
||||
ActivitySummaryData summarySubdata = activitySummaryJsonSummary.getSummaryData(false);
|
||||
|
||||
if (summarySubdata != null) {
|
||||
if (summarySubdata.has("caloriesBurnt")) {
|
||||
caloriesBurntSum += summarySubdata.getNumber("caloriesBurnt", 0).doubleValue();
|
||||
}
|
||||
if (summarySubdata.has("distanceMeters")) {
|
||||
distanceSum += summarySubdata.getNumber("distanceMeters", 0).doubleValue();
|
||||
}
|
||||
if (summarySubdata.has("activeSeconds")) {
|
||||
activeSecondsSum += summarySubdata.getNumber("activeSeconds", 0).doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new StatsContainer(
|
||||
durationSum,
|
||||
caloriesBurntSum,
|
||||
distanceSum,
|
||||
activeSecondsSum,
|
||||
firstItemDate,
|
||||
lastItemDate,
|
||||
activitySame ? activityIcon : 0
|
||||
);
|
||||
}
|
||||
|
||||
public static StatsContainer fromJson(final String json) {
|
||||
return GSON.fromJson(json, StatsContainer.class);
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return GSON.toJson(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -101,7 +101,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutListActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.BatteryInfoActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders;
|
||||
@@ -573,7 +573,7 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent startIntent;
|
||||
startIntent = new Intent(context, ActivitySummariesActivity.class);
|
||||
startIntent = new Intent(context, WorkoutListActivity.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
context.startActivity(startIntent);
|
||||
}
|
||||
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutListViewModel
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityListItem
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class WorkoutSummariesAdapter(
|
||||
context: Context,
|
||||
private val device: GBDevice,
|
||||
private var activityKindFilter: Int,
|
||||
private var dateFromFilter: Long,
|
||||
private var dateToFilter: Long,
|
||||
private var nameContainsFilter: String?,
|
||||
private var deviceFilter: Long,
|
||||
private var itemsFilter: List<Long>?
|
||||
) : AbstractActivityListingAdapter<BaseActivitySummary>(context) {
|
||||
var dashboardStats: WorkoutListViewModel.DashboardStats? = null
|
||||
var isDashboardLoading: Boolean = false
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): AbstractActivityListingViewHolder<BaseActivitySummary> {
|
||||
return when (viewType) {
|
||||
0 -> // dashboard
|
||||
DashboardViewHolder(
|
||||
LayoutInflater.from(context).inflate(R.layout.activity_summary_dashboard_item, parent, false)
|
||||
)
|
||||
|
||||
2 -> // item
|
||||
ActivityItemViewHolder(
|
||||
device,
|
||||
LayoutInflater.from(context).inflate(R.layout.activity_list_item, parent, false)
|
||||
)
|
||||
|
||||
else -> super.onCreateViewHolder(parent, viewType)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setActivityKindFilter(filter: Int) {
|
||||
activityKindFilter = filter
|
||||
}
|
||||
|
||||
override fun setDateFromFilter(date: Long) {
|
||||
dateFromFilter = date
|
||||
}
|
||||
|
||||
override fun setDateToFilter(date: Long) {
|
||||
dateToFilter = date
|
||||
}
|
||||
|
||||
override fun setNameContainsFilter(name: String?) {
|
||||
nameContainsFilter = name
|
||||
}
|
||||
|
||||
override fun setItemsFilter(items: List<Long>?) {
|
||||
itemsFilter = items
|
||||
}
|
||||
|
||||
override fun setDeviceFilter(device: Long) {
|
||||
deviceFilter = device
|
||||
}
|
||||
|
||||
fun getActivityKindFilter(): Int = activityKindFilter
|
||||
|
||||
class ActivityItemViewHolder(val device: GBDevice, itemView: View) : AbstractActivityListingViewHolder<BaseActivitySummary>(itemView) {
|
||||
private val activityListItem = ActivityListItem(itemView)
|
||||
|
||||
override fun fill(position: Int, summary: BaseActivitySummary, selected: Boolean) {
|
||||
val parser = device.deviceCoordinator.getActivitySummaryParser(device, itemView.context)
|
||||
val workout = parser.parseWorkout(summary, false)
|
||||
|
||||
val hasGps = when {
|
||||
workout.summary.gpxTrack != null -> true
|
||||
workout.summary.summaryData?.contains(ActivitySummaryEntries.INTERNAL_HAS_GPS) == true -> {
|
||||
workout.data.getBoolean(ActivitySummaryEntries.INTERNAL_HAS_GPS, false)
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
activityListItem.update(
|
||||
null,
|
||||
null,
|
||||
ActivityKind.fromCode(summary.activityKind),
|
||||
summary.name,
|
||||
-1,
|
||||
-1f,
|
||||
-1,
|
||||
-1f,
|
||||
summary.endTime.time - summary.startTime.time,
|
||||
hasGps,
|
||||
summary.startTime,
|
||||
position % 2 == 1,
|
||||
selected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
inner class DashboardViewHolder(itemView: View) : AbstractActivityListingViewHolder<BaseActivitySummary>(itemView) {
|
||||
private val durationSumView: TextView = itemView.findViewById(R.id.summary_dashboard_layout_duration_label)
|
||||
private val caloriesBurntSumView: TextView = itemView.findViewById(R.id.summary_dashboard_layout_calories_label)
|
||||
private val distanceSumView: TextView = itemView.findViewById(R.id.summary_dashboard_layout_distance_label)
|
||||
private val activeSecondsSumView: TextView =
|
||||
itemView.findViewById(R.id.summary_dashboard_layout_active_duration_label)
|
||||
private val timeStartView: TextView = itemView.findViewById(R.id.summary_dashboard_layout_from_label)
|
||||
private val timeEndView: TextView = itemView.findViewById(R.id.summary_dashboard_layout_to_label)
|
||||
private val activitiesCountView: TextView = itemView.findViewById(R.id.summary_dashboard_layout_count_label)
|
||||
private val activityKindView: TextView = itemView.findViewById(R.id.summary_dashboard_layout_activity_label)
|
||||
private val activityIconView: ImageView = itemView.findViewById(R.id.summary_dashboard_layout_activity_icon)
|
||||
private val activityIconBigView: ImageView =
|
||||
itemView.findViewById(R.id.summary_dashboard_layout_big_activity_icon)
|
||||
private val loadingSpinner: ProgressBar = itemView.findViewById(R.id.summary_dashboard_layout_loading)
|
||||
private val contentLayout: View = itemView.findViewById(R.id.summary_dashboard_layout_content)
|
||||
|
||||
override fun fill(position: Int, summary: BaseActivitySummary, selected: Boolean) {
|
||||
loadingSpinner.visibility = if (isDashboardLoading) View.VISIBLE else View.INVISIBLE
|
||||
contentLayout.visibility = if (!isDashboardLoading) View.VISIBLE else View.INVISIBLE
|
||||
|
||||
if (isDashboardLoading || dashboardStats == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val stats = dashboardStats!!
|
||||
val activitiesCount = itemCount - 2 // remove dashboard and end spacer
|
||||
|
||||
durationSumView.text = DateTimeUtils.formatDurationHoursMinutes(stats.durationSum, TimeUnit.MILLISECONDS)
|
||||
caloriesBurntSumView.text =
|
||||
String.format("%s %s", stats.caloriesBurntSum.toLong(), context.getString(R.string.calories_unit))
|
||||
distanceSumView.text = FormatUtils.getFormattedDistanceLabel(stats.distanceSum)
|
||||
activeSecondsSumView.text =
|
||||
DateTimeUtils.formatDurationHoursMinutes(stats.activeSecondsSum, TimeUnit.SECONDS)
|
||||
activitiesCountView.text = activitiesCount.toString()
|
||||
|
||||
activityKindView.text = when {
|
||||
activityKindFilter != 0 -> {
|
||||
val activityKind = ActivityKind.fromCode(activityKindFilter)
|
||||
activityIconView.setImageResource(activityKind.icon)
|
||||
activityIconBigView.setImageResource(activityKind.icon)
|
||||
activityKind.getLabel(context)
|
||||
}
|
||||
|
||||
stats.activityIcon != 0 -> {
|
||||
val activityKind = ActivityKind.fromCode(stats.activityIcon)
|
||||
activityIconView.setImageResource(activityKind.icon)
|
||||
activityIconBigView.setImageResource(activityKind.icon)
|
||||
context.getString(R.string.activity_summaries_all_activities)
|
||||
}
|
||||
|
||||
else -> {
|
||||
activityIconView.setImageResource(R.drawable.ic_activity_unknown_small)
|
||||
activityIconBigView.setImageResource(R.drawable.ic_activity_unknown_small)
|
||||
context.getString(R.string.activity_summaries_all_activities)
|
||||
}
|
||||
}
|
||||
|
||||
// start and end are inverted when filter not applied, because items are sorted the other way
|
||||
timeStartView.text = if (dateFromFilter != 0L) {
|
||||
DateTimeUtils.formatDate(Date(dateFromFilter))
|
||||
} else {
|
||||
DateTimeUtils.formatDate(Date(stats.lastItemDate))
|
||||
}
|
||||
|
||||
timeEndView.text = if (dateToFilter != 0L) {
|
||||
DateTimeUtils.formatDate(Date(dateToFilter))
|
||||
} else {
|
||||
DateTimeUtils.formatDate(Date(stats.firstItemDate))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,476 +1,489 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/list_item_parent_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!--
|
||||
android:background="?android:attr/activatedBackgroundIndicator"
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard"
|
||||
<FrameLayout
|
||||
android:id="@+id/summary_dashboard_layout_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginBottom="10dp">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/summary_dashboard_layout_loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_big_activity_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_weight="1"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_big_activity_icon"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_activity_unknown_small" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_distance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_distance_icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
app:srcCompat="@drawable/ic_distance" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_distance_title"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_distance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/distance"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_distance_label"
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_distance_icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_distance" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_distance_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:labelFor="@id/summary_dashboard_layout_distance_label"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/distance"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_distance_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15000"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_calories"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15000"
|
||||
android:textSize="18sp" />
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="bottom|center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_calories_icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_calories" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_calories_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:labelFor="@id/summary_dashboard_layout_calories_label"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/caloriesBurnt"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_calories_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="30000"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_calories"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="bottom|center"
|
||||
android:orientation="vertical">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_calories_icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
app:srcCompat="@drawable/ic_calories" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_calories_title"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_duration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/caloriesBurnt"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_calories_label"
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_duration_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_timer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_duration_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:labelFor="@id/summary_dashboard_layout_duration_label"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_detail_duration_label"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_duration_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="1d 20h 35m"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_active_duration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="30000"
|
||||
android:textSize="18sp" />
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_active_duration_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_timer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_active_duration_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:labelFor="@id/summary_dashboard_layout_active_duration_label"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_list_summary_active_time"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="bottom|center"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_active_duration_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="1d 13h 30m"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_duration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_duration_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
app:srcCompat="@drawable/ic_timer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_duration_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_detail_duration_label"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_duration_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="1d 20h 35m"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_active_duration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_active_duration_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
app:srcCompat="@drawable/ic_timer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_active_duration_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_list_summary_active_time"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_active_duration_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="1d 13h 30m"
|
||||
android:textSize="18sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_activity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_activity_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
app:srcCompat="@drawable/ic_activity_unknown_small" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_activity_title"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_activity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/Activity"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_activity_label"
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_activity_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_activity_unknown_small" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_activity_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:labelFor="@id/summary_dashboard_layout_activity_label"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/Activity"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_activity_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="All Activities"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="All Activities"
|
||||
android:textSize="18sp" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_count_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_activity_stacked" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_count_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:labelFor="@id/summary_dashboard_layout_count_label"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_list_summary_activities"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_count_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="bottom|center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_count_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
app:srcCompat="@drawable/ic_activity_stacked" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_count_title"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_from"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_list_summary_activities"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_count_label"
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_from_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_date_range" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_from_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:labelFor="@id/summary_dashboard_layout_from_label"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_filter_date_from"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_from_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="January 1, 1970"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_to"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="15"
|
||||
android:textSize="18sp" />
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_to_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_date_range" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_to_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:labelFor="@id/summary_dashboard_layout_to_label"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_filter_date_to"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_to_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="December 1, 2020"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="bottom|center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_from"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_from_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
app:srcCompat="@drawable/ic_date_range" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_from_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_filter_date_from"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_from_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="January 1, 1970"
|
||||
android:textSize="18sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summary_dashboard_layout_to"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|top"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_dashboard_layout_to_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
app:srcCompat="@drawable/ic_date_range" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_to_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="@string/activity_filter_date_to"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_dashboard_layout_to_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="December 1, 2020"
|
||||
android:textSize="18sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:layout_below="@id/summary_dashboard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/row_separator"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
android:layout_below="@id/summary_dashboard_layout_content"
|
||||
android:background="?attr/row_separator" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
Reference in New Issue
Block a user