forked from upstream-mirrors/OpenTracks
Merge pull request #1687 from lmarz/track
TrackListActivity: Switch to RecyclerView Fixes #1613
This commit is contained in:
@@ -333,7 +333,8 @@ limitations under the License.
|
||||
|
||||
<activity
|
||||
android:name=".TrackListActivity"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -26,37 +25,27 @@ import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
@@ -66,15 +55,14 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.share.ShareUtils;
|
||||
import de.dennisguse.opentracks.ui.TrackListAdapter;
|
||||
import de.dennisguse.opentracks.ui.aggregatedStatistics.AggregatedStatisticsActivity;
|
||||
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.ui.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An activity displaying a list of tracks.
|
||||
@@ -87,12 +75,10 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
// The following are set in onCreate
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private ResourceCursorAdapter resourceCursorAdapter;
|
||||
private TrackListAdapter adapter;
|
||||
|
||||
private TrackListBinding viewBinding;
|
||||
|
||||
private final TrackLoaderCallBack loaderCallbacks = new TrackLoaderCallBack();
|
||||
|
||||
// Preferences
|
||||
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
|
||||
|
||||
@@ -128,11 +114,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||
unitSystem = PreferencesUtils.getUnitSystem();
|
||||
if (adapter != null) {
|
||||
adapter.updateUnitSystem(unitSystem);
|
||||
}
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(() -> {
|
||||
TrackListActivity.this.invalidateOptionsMenu();
|
||||
loaderCallbacks.restart();
|
||||
loadData();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -140,6 +129,8 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
// Menu items
|
||||
private MenuItem searchMenuItem;
|
||||
|
||||
private String searchQuery;
|
||||
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> {
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(TrackListActivity.this, this::onRecordingStatusChanged);
|
||||
@@ -180,63 +171,10 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackIdId) -> {
|
||||
Track.Id trackId = new Track.Id(trackIdId);
|
||||
if (recordingStatus.isRecording() && trackId.equals(recordingStatus.getTrackId())) {
|
||||
// Is recording -> open record activity.
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
this,
|
||||
new Pair<>(view.findViewById(R.id.list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
||||
startActivity(newIntent, activityOptions.toBundle());
|
||||
}
|
||||
});
|
||||
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
|
||||
int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
||||
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
|
||||
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
|
||||
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
|
||||
int startTimeOffsetIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME_OFFSET);
|
||||
int activityTypeIndex = cursor.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE_LOCALIZED);
|
||||
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
||||
int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
|
||||
|
||||
Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
|
||||
boolean isRecording = trackId.equals(recordingStatus.getTrackId());
|
||||
String icon = cursor.getString(iconIndex);
|
||||
int iconId = ActivityType.findBy(icon)
|
||||
.getIconDrawableId();
|
||||
String name = cursor.getString(nameIndex);
|
||||
String totalTime = StringUtils.formatElapsedTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
|
||||
String totalDistance = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(TrackListActivity.this).formatDistance(Distance.of(cursor.getDouble(totalDistanceIndex)));
|
||||
int markerCount = cursor.getInt(markerCountIndex);
|
||||
long startTime = cursor.getLong(startTimeIndex);
|
||||
int startTimeOffset = cursor.getInt(startTimeOffsetIndex);
|
||||
String activityType = icon != null && !icon.equals("") ? null : cursor.getString(activityTypeIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
|
||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording,
|
||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneOffset.ofTotalSeconds(startTimeOffset)),
|
||||
activityType, description, false);
|
||||
}
|
||||
};
|
||||
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
||||
ActivityUtils.configureListViewContextualMenu(viewBinding.trackList, contextualActionModeCallback);
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
|
||||
adapter = new TrackListAdapter(this, viewBinding.trackList, recordingStatus, unitSystem);
|
||||
viewBinding.trackList.setLayoutManager(layoutManager);
|
||||
viewBinding.trackList.setAdapter(adapter);
|
||||
|
||||
viewBinding.trackListFabAction.setOnClickListener((view) -> {
|
||||
if (recordingStatus.isRecording()) {
|
||||
@@ -271,8 +209,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
});
|
||||
|
||||
setSupportActionBar(viewBinding.trackListToolbar);
|
||||
|
||||
loadData(getIntent());
|
||||
adapter.setActionModeCallback(contextualActionModeCallback);
|
||||
}
|
||||
|
||||
private void requestRequiredPermissions() {
|
||||
@@ -293,7 +230,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
// Update UI
|
||||
this.invalidateOptionsMenu();
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
||||
loadData();
|
||||
|
||||
// Float button
|
||||
setFloatButton();
|
||||
@@ -312,6 +249,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
super.onDestroy();
|
||||
viewBinding = null;
|
||||
trackRecordingServiceConnection = null;
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -333,10 +271,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
updateGpsMenuItem(gpsStatusValue.isGpsStarted(), recordingStatus.isRecording());
|
||||
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
searchView.setQuery("", false);
|
||||
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -375,11 +309,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overridePendingTransition(int enterAnim, int exitAnim) {
|
||||
//Disable animations as it is weird going into searchMode; looks okay for SplashScreen.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
@@ -387,8 +316,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
searchView.setIconified(true);
|
||||
}
|
||||
|
||||
if (loaderCallbacks.getSearchQuery() != null) {
|
||||
loaderCallbacks.setSearch(null);
|
||||
if (searchQuery != null) {
|
||||
searchQuery = null;
|
||||
loadData();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -396,19 +326,30 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
loadData(intent);
|
||||
}
|
||||
|
||||
private void loadData(Intent intent) {
|
||||
String searchQuery = null;
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
searchQuery = intent.getStringExtra(SearchManager.QUERY);
|
||||
} else {
|
||||
searchQuery = null;
|
||||
}
|
||||
}
|
||||
|
||||
loaderCallbacks.setSearch(searchQuery);
|
||||
private void loadData() {
|
||||
viewBinding.trackListToolbar.setTitle(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.app_name)));
|
||||
|
||||
Cursor tracks = new ContentProviderUtils(this).searchTracks(searchQuery);
|
||||
|
||||
adapter.swapData(tracks);
|
||||
|
||||
if (tracks.getCount() == 0) {
|
||||
viewBinding.trackListEmptyView.setVisibility(View.VISIBLE);
|
||||
viewBinding.trackList.setVisibility(View.GONE);
|
||||
} else {
|
||||
viewBinding.trackListEmptyView.setVisibility(View.GONE);
|
||||
viewBinding.trackList.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -490,64 +431,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_select_all) {
|
||||
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
||||
viewBinding.trackList.setItemChecked(i, true);
|
||||
}
|
||||
adapter.setAllSelected(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class TrackLoaderCallBack implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private String searchQuery = null;
|
||||
|
||||
public String getSearchQuery() {
|
||||
return searchQuery;
|
||||
}
|
||||
|
||||
public void setSearch(String searchQuery) {
|
||||
this.searchQuery = searchQuery;
|
||||
restart();
|
||||
viewBinding.trackListToolbar.setTitle(searchQuery == null ? getString(R.string.app_name) : searchQuery);
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||
final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
|
||||
TracksColumns.DESCRIPTION, TracksColumns.ACTIVITY_TYPE_LOCALIZED, TracksColumns.STARTTIME, TracksColumns.STARTTIME_OFFSET,
|
||||
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON, TracksColumns.MARKER_COUNT};
|
||||
|
||||
final String sortOrder = TracksColumns.STARTTIME + " DESC";
|
||||
|
||||
if (searchQuery == null) {
|
||||
return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, null, null, sortOrder);
|
||||
} else {
|
||||
final String SEARCH_QUERY = TracksColumns.NAME + " LIKE ? OR " +
|
||||
TracksColumns.DESCRIPTION + " LIKE ? OR " +
|
||||
TracksColumns.ACTIVITY_TYPE_LOCALIZED + " LIKE ?";
|
||||
final String[] selectionArgs = new String[]{"%" + searchQuery + "%", "%" + searchQuery + "%", "%" + searchQuery + "%"};
|
||||
return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, SEARCH_QUERY, selectionArgs, sortOrder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
|
||||
resourceCursorAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
|
||||
resourceCursorAdapter.swapCursor(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void onGpsStatusChanged(GpsStatusValue newStatus) {
|
||||
gpsStatusValue = newStatus;
|
||||
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||
@@ -561,5 +451,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
setFloatButton();
|
||||
adapter.updateRecordingStatus(recordingStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,31 @@ public class ContentProviderUtils {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public Cursor searchTracks(String searchQuery) {
|
||||
// Needed, because MARKER_COUNT is a virtual column and has to be explicitly requested.
|
||||
final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.UUID, TracksColumns.NAME,
|
||||
TracksColumns.DESCRIPTION, TracksColumns.ACTIVITY_TYPE_LOCALIZED, TracksColumns.STARTTIME,
|
||||
TracksColumns.STARTTIME_OFFSET, TracksColumns.STOPTIME, TracksColumns.MARKER_COUNT,
|
||||
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.MOVINGTIME,
|
||||
TracksColumns.AVGSPEED, TracksColumns.AVGMOVINGSPEED, TracksColumns.MAXSPEED,
|
||||
TracksColumns.MIN_ALTITUDE, TracksColumns.MAX_ALTITUDE, TracksColumns.ALTITUDE_GAIN,
|
||||
TracksColumns.ALTITUDE_LOSS, TracksColumns.ICON
|
||||
};
|
||||
|
||||
String selection = null;
|
||||
String[] selectionArgs = null;
|
||||
final String sortOrder = TracksColumns.STARTTIME + " DESC";
|
||||
|
||||
if (searchQuery != null) {
|
||||
selection = TracksColumns.NAME + " LIKE ? OR " +
|
||||
TracksColumns.DESCRIPTION + " LIKE ? OR " +
|
||||
TracksColumns.ACTIVITY_TYPE_LOCALIZED + " LIKE ?";
|
||||
selectionArgs = new String[]{"%" + searchQuery + "%", "%" + searchQuery + "%", "%" + searchQuery + "%"};
|
||||
}
|
||||
|
||||
return contentResolver.query(TracksColumns.CONTENT_URI, PROJECTION, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
public Track getTrack(@NonNull Track.Id trackId) {
|
||||
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())}, null)) {
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
package de.dennisguse.opentracks.ui;
|
||||
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.TrackListItemBinding;
|
||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.ui.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public class TrackListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ActionMode.Callback {
|
||||
|
||||
private static final String TAG = TrackListAdapter.class.getSimpleName();
|
||||
|
||||
private final AppCompatActivity context;
|
||||
private final RecyclerView recyclerView;
|
||||
private final SparseBooleanArray selection = new SparseBooleanArray();
|
||||
private RecordingStatus recordingStatus;
|
||||
private UnitSystem unitSystem;
|
||||
private Cursor tracks;
|
||||
private boolean selectionMode = false;
|
||||
private ActivityUtils.ContextualActionModeCallback actionModeCallback;
|
||||
private ActionMode actionMode;
|
||||
|
||||
public TrackListAdapter(AppCompatActivity context, RecyclerView recyclerView, RecordingStatus recordingStatus, UnitSystem unitSystem) {
|
||||
this.context = context;
|
||||
this.recyclerView = recyclerView;
|
||||
this.recordingStatus = recordingStatus;
|
||||
this.unitSystem = unitSystem;
|
||||
}
|
||||
|
||||
public void setActionModeCallback(ActivityUtils.ContextualActionModeCallback actionModeCallback) {
|
||||
this.actionModeCallback = actionModeCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.track_list_item, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
ViewHolder viewHolder = (ViewHolder) holder;
|
||||
|
||||
tracks.moveToPosition(position);
|
||||
viewHolder.bind(tracks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (tracks == null) {
|
||||
return 0;
|
||||
}
|
||||
return tracks.getCount();
|
||||
}
|
||||
|
||||
public void swapData(Cursor tracks) {
|
||||
this.tracks = tracks;
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void updateRecordingStatus(RecordingStatus recordingStatus) {
|
||||
this.recordingStatus = recordingStatus;
|
||||
}
|
||||
|
||||
public void updateUnitSystem(UnitSystem unitSystem) {
|
||||
this.unitSystem = unitSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
mode.getMenuInflater().inflate(R.menu.list_context_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
selectionMode = true;
|
||||
actionModeCallback.onPrepare(menu, getCheckedPositions(), getCheckedIds(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
if (actionModeCallback.onClick(item.getItemId(), getCheckedPositions(), getCheckedIds())) {
|
||||
mode.finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
selectionMode = false;
|
||||
|
||||
setAllSelected(false);
|
||||
selection.clear();
|
||||
|
||||
actionModeCallback.onDestroy();
|
||||
}
|
||||
|
||||
public void setAllSelected(boolean isSelected) {
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
ViewHolder holder = (ViewHolder) recyclerView.findViewHolderForLayoutPosition(i);
|
||||
holder.setSelected(isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] getCheckedPositions() {
|
||||
List<Integer> positions = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < selection.size(); i++) {
|
||||
if (selection.valueAt(i)) {
|
||||
positions.add(selection.keyAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
return positions.stream().mapToInt(i -> i).toArray();
|
||||
}
|
||||
|
||||
private long[] getCheckedIds() {
|
||||
List<Long> ids = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < selection.size(); i++) {
|
||||
if (selection.valueAt(i)) {
|
||||
ViewHolder holder = (ViewHolder) recyclerView.findViewHolderForLayoutPosition(selection.keyAt(i));
|
||||
ids.add(holder.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return ids.stream().mapToLong(i -> i).toArray();
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
private final TrackListItemBinding viewBinding;
|
||||
private final View view;
|
||||
|
||||
private ActivityType activityType;
|
||||
private String name;
|
||||
private int markerCount;
|
||||
private Duration totalTime;
|
||||
private Distance totalDistance;
|
||||
private Instant startTime;
|
||||
private ZoneOffset zoneOffset;
|
||||
private String activityTypeLocalized;
|
||||
private String description;
|
||||
private Track.Id trackId;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
|
||||
viewBinding = TrackListItemBinding.bind(itemView);
|
||||
view = itemView;
|
||||
|
||||
view.setOnClickListener(this);
|
||||
view.setOnLongClickListener(this);
|
||||
}
|
||||
|
||||
private void getData(Cursor track) {
|
||||
final int idIndex = track.getColumnIndexOrThrow(TracksColumns._ID);
|
||||
final int nameIndex = track.getColumnIndexOrThrow(TracksColumns.NAME);
|
||||
final int descriptionIndex = track.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
||||
final int activityTypeLocalizedIndex = track.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE_LOCALIZED);
|
||||
final int startTimeIndex = track.getColumnIndexOrThrow(TracksColumns.STARTTIME);
|
||||
final int startTimeOffsetIndex = track.getColumnIndexOrThrow(TracksColumns.STARTTIME_OFFSET);
|
||||
final int totalDistanceIndex = track.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
|
||||
final int totalTimeIndex = track.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
|
||||
final int iconIndex = track.getColumnIndexOrThrow(TracksColumns.ICON);
|
||||
final int markerCountIndex = track.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
|
||||
|
||||
activityType = ActivityType.findBy(track.getString(iconIndex));
|
||||
name = track.getString(nameIndex);
|
||||
markerCount = track.getInt(markerCountIndex);
|
||||
totalTime = Duration.ofMillis(track.getLong(totalTimeIndex));
|
||||
totalDistance = Distance.of(track.getFloat(totalDistanceIndex));
|
||||
startTime = Instant.ofEpochMilli(track.getLong(startTimeIndex));
|
||||
zoneOffset = ZoneOffset.ofTotalSeconds(track.getInt(startTimeOffsetIndex));
|
||||
activityTypeLocalized = track.getString(activityTypeLocalizedIndex);
|
||||
description = track.getString(descriptionIndex);
|
||||
trackId = new Track.Id(track.getLong(idIndex));
|
||||
}
|
||||
|
||||
public void bind(Cursor track) {
|
||||
|
||||
getData(track);
|
||||
|
||||
int iconId = activityType.getIconDrawableId();
|
||||
int iconDesc = R.string.image_track;
|
||||
|
||||
if (recordingStatus.isRecording()) {
|
||||
iconId = R.drawable.ic_track_recording;
|
||||
iconDesc = R.string.image_record;
|
||||
}
|
||||
|
||||
viewBinding.trackListItemIcon.setImageResource(iconId);
|
||||
viewBinding.trackListItemIcon.setContentDescription(context.getString(iconDesc));
|
||||
|
||||
viewBinding.trackListItemName.setText(name);
|
||||
|
||||
String timeDistanceText = ListItemUtils.getTimeDistanceText(context, unitSystem, recordingStatus.isRecording(), totalTime, totalDistance, markerCount);
|
||||
viewBinding.trackListItemTimeDistance.setText(timeDistanceText);
|
||||
|
||||
viewBinding.trackListItemMarkerCountIcon.setVisibility(markerCount > 0 ? View.VISIBLE : View.GONE);
|
||||
viewBinding.trackListItemMarkerCount.setText(markerCount > 0 ? Integer.toString(markerCount) : null);
|
||||
|
||||
if (!recordingStatus.isRecording()) {
|
||||
ListItemUtils.setDateAndTime(context, viewBinding.trackListItemDate, viewBinding.trackListItemTime, startTime, zoneOffset);
|
||||
} else {
|
||||
viewBinding.trackListItemDate.setText(null);
|
||||
viewBinding.trackListItemTime.setText(null);
|
||||
}
|
||||
|
||||
String category = activityType == null ? activityTypeLocalized : null;
|
||||
String categoryDescription = StringUtils.getCategoryDescription(category, description);
|
||||
viewBinding.trackListItemCategoryDescription.setText(categoryDescription);
|
||||
viewBinding.trackListItemCategoryDescription.setVisibility(categoryDescription.equals("") ? View.GONE : View.VISIBLE);
|
||||
|
||||
setSelected(selection.get(getLayoutPosition()));
|
||||
}
|
||||
|
||||
public void setSelected(boolean isSelected) {
|
||||
selection.put(getLayoutPosition(), isSelected);
|
||||
view.setActivated(isSelected);
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return trackId.id();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (selectionMode) {
|
||||
setSelected(!view.isActivated());
|
||||
actionMode.invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (recordingStatus.isRecording() && trackId.equals(recordingStatus.getTrackId())) {
|
||||
// Is recording -> open record activity.
|
||||
Intent newIntent = IntentUtils.newIntent(context, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
context.startActivity(newIntent);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
Intent newIntent = IntentUtils.newIntent(context, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
context,
|
||||
new Pair<>(view.findViewById(R.id.track_list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
||||
context.startActivity(newIntent, activityOptions.toBundle());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
actionMode = context.startSupportActionMode(TrackListAdapter.this);
|
||||
setSelected(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,18 +18,20 @@ package de.dennisguse.opentracks.ui.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -42,101 +44,6 @@ public class ListItemUtils {
|
||||
private ListItemUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a list item.
|
||||
*
|
||||
* @param context the context
|
||||
* @param view the list item view
|
||||
* @param isRecording true if recording
|
||||
* @param iconId the icon id
|
||||
* @param iconContentDescriptionId the icon content description id
|
||||
* @param name the name value
|
||||
* @param totalTime the total time value
|
||||
* @param totalDistance the total distance value
|
||||
* @param markerCount the marker count
|
||||
* @param offsetDateTime the start time with offset
|
||||
* @param category the category value
|
||||
* @param description the description value
|
||||
* @param hasPhoto true if this list item has photo
|
||||
*/
|
||||
public static void setListItem(Context context, View view, boolean isRecording, int iconId, int iconContentDescriptionId, String name, String totalTime, String totalDistance, int markerCount, OffsetDateTime offsetDateTime, String category, String description, boolean hasPhoto) {
|
||||
// Set icon
|
||||
if (isRecording) {
|
||||
iconId = R.drawable.ic_track_recording;
|
||||
iconContentDescriptionId = R.string.image_record;
|
||||
}
|
||||
|
||||
ImageView iconImageView = view.findViewById(R.id.list_item_icon);
|
||||
iconImageView.setImageResource(iconId);
|
||||
iconImageView.setContentDescription(context.getString(iconContentDescriptionId));
|
||||
|
||||
// Set name
|
||||
TextView nameTextView = view.findViewById(R.id.list_item_name);
|
||||
setTextView(context, nameTextView, name, hasPhoto);
|
||||
|
||||
// Set totalTime/totalDistance
|
||||
TextView timeDistanceTextView = view.findViewById(R.id.list_item_time_distance);
|
||||
String timeDistanceText;
|
||||
if (isRecording) {
|
||||
timeDistanceText = context.getString(R.string.generic_recording);
|
||||
} else {
|
||||
// Match list_item_time_distance in list_item.xml
|
||||
timeDistanceText = getTimeDistance(totalTime, totalDistance);
|
||||
if (markerCount > 0) {
|
||||
timeDistanceText += " ‧";
|
||||
}
|
||||
}
|
||||
setTextView(context, timeDistanceTextView, timeDistanceText, hasPhoto);
|
||||
|
||||
// Set markerCount
|
||||
ImageView markerCountIcon = view.findViewById(R.id.list_item_marker_count_icon);
|
||||
TextView markerCountTextView = view.findViewById(R.id.list_item_marker_count);
|
||||
boolean hasMarker = markerCount > 0;
|
||||
markerCountIcon.setVisibility(hasMarker ? View.VISIBLE : View.GONE);
|
||||
String markerCountValue = hasMarker ? String.valueOf(markerCount) : null;
|
||||
if (hasMarker) {
|
||||
// Scale markerCountIcon
|
||||
int lineHeight = markerCountTextView.getLineHeight();
|
||||
LayoutParams layoutParams = markerCountIcon.getLayoutParams();
|
||||
layoutParams.width = lineHeight;
|
||||
layoutParams.height = lineHeight;
|
||||
}
|
||||
setTextView(context, markerCountTextView, markerCountValue, hasPhoto);
|
||||
|
||||
// Set date/time
|
||||
TextView dateTextView = view.findViewById(R.id.list_item_date);
|
||||
TextView timeTextView = view.findViewById(R.id.list_item_time);
|
||||
String dateValue = null;
|
||||
String timeValue = null;
|
||||
if (!isRecording) {
|
||||
dateValue = StringUtils.formatDateTodayRelative(context, offsetDateTime);
|
||||
String pattern = "HH:mm";
|
||||
if (!offsetDateTime.getOffset().equals(OffsetDateTime.now().getOffset())) {
|
||||
pattern = "HH:mm x";
|
||||
}
|
||||
timeValue = offsetDateTime.format(DateTimeFormatter.ofPattern(pattern));
|
||||
}
|
||||
setTextView(context, dateTextView, dateValue, hasPhoto);
|
||||
setTextView(context, timeTextView, timeValue, hasPhoto);
|
||||
|
||||
// Set category and description
|
||||
TextView categoryDescriptionTextView = view.findViewById(R.id.list_item_category_description);
|
||||
String categoryDescription = isRecording ? null : StringUtils.getCategoryDescription(category, description);
|
||||
|
||||
// Place categoryDescription in either ownerTimeDistanceTextView or categoryDescriptionTextView
|
||||
if (timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE) {
|
||||
setTextView(context, categoryDescriptionTextView, null, hasPhoto);
|
||||
// Match list_item_category_description in list_item.xml
|
||||
timeDistanceTextView.setSingleLine(false);
|
||||
timeDistanceTextView.setMaxLines(2);
|
||||
setTextView(context, timeDistanceTextView, categoryDescription, hasPhoto);
|
||||
} else {
|
||||
// Match list_item_time_distance in list_item.xml
|
||||
timeDistanceTextView.setSingleLine(true);
|
||||
setTextView(context, categoryDescriptionTextView, categoryDescription, hasPhoto);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string for share owner, total time, and total distance.
|
||||
*
|
||||
@@ -160,6 +67,29 @@ public class ListItemUtils {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String getTimeDistanceText(Context context, UnitSystem unitSystem, boolean isRecording, Duration totalTime, Distance totalDistance, int markerCount) {
|
||||
String timeDistanceText;
|
||||
if (isRecording) {
|
||||
timeDistanceText = context.getString(R.string.generic_recording);
|
||||
} else {
|
||||
// Match list_item_time_distance in list_item.xml
|
||||
|
||||
String time = StringUtils.formatElapsedTime(totalTime);
|
||||
// TODO unit system
|
||||
String distance = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(context)
|
||||
.formatDistance(totalDistance);
|
||||
|
||||
timeDistanceText = getTimeDistance(time, distance);
|
||||
if (markerCount > 0) {
|
||||
timeDistanceText += " ‧";
|
||||
}
|
||||
}
|
||||
|
||||
return timeDistanceText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a text view.
|
||||
*
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@drawable/activated_background"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<!-- Photo -->
|
||||
<ImageView
|
||||
android:id="@+id/list_item_photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:contentDescription="@string/image_photo_marker"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<!-- A gradient on top of the text -->
|
||||
<ImageView
|
||||
android:id="@+id/list_item_text_gradient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:alpha=".6"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:contentDescription="@string/image_photo_marker"
|
||||
android:src="@drawable/text_gradient" />
|
||||
|
||||
<!-- Icon -->
|
||||
<ImageView
|
||||
android:id="@+id/list_item_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:contentDescription="@string/image_track"
|
||||
tools:src="@drawable/ic_activity_bike_24dp"/>
|
||||
|
||||
<!-- Name -->
|
||||
<TextView
|
||||
android:id="@+id/list_item_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/textAppearanceBody1"
|
||||
android:singleLine="true"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/list_item_icon"
|
||||
app:layout_constraintEnd_toStartOf="@id/list_item_date"
|
||||
tools:text="Main text" />
|
||||
|
||||
<!-- Date -->
|
||||
<TextView
|
||||
android:id="@+id/list_item_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/list_item_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:gravity="end"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="2022-02-17" />
|
||||
|
||||
<!-- Total time and distance -->
|
||||
<TextView
|
||||
android:id="@+id/list_item_time_distance"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
app:layout_constraintTop_toBottomOf="@id/list_item_name"
|
||||
app:layout_constraintStart_toStartOf="@id/list_item_name"
|
||||
tools:text="50:43 (50.00km)" />
|
||||
|
||||
<!-- Marker count icon -->
|
||||
<ImageView
|
||||
android:id="@+id/list_item_marker_count_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/list_item_name"
|
||||
app:layout_constraintStart_toEndOf="@id/list_item_time_distance"
|
||||
app:layout_constraintBottom_toBottomOf="@id/list_item_time_distance"
|
||||
android:contentDescription="@string/image_marker_count"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_marker_show_24dp" />
|
||||
|
||||
<!-- Marker count text -->
|
||||
<TextView
|
||||
android:id="@+id/list_item_marker_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
app:layout_constraintStart_toEndOf="@id/list_item_marker_count_icon"
|
||||
app:layout_constraintBottom_toBottomOf="@id/list_item_marker_count_icon"
|
||||
android:singleLine="true"
|
||||
tools:text="2" />
|
||||
|
||||
<!-- Time -->
|
||||
<TextView
|
||||
android:id="@+id/list_item_time"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
app:layout_constraintTop_toBottomOf="@id/list_item_name"
|
||||
app:layout_constraintStart_toEndOf="@id/list_item_marker_count"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:gravity="end"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="10:10:10"/>
|
||||
|
||||
<!-- Second row barrier -->
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/list_item_second_row_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="list_item_time_distance,list_item_marker_count_icon,list_item_marker_count,list_item_time" />
|
||||
|
||||
<!-- Category and description -->
|
||||
<TextView
|
||||
android:id="@+id/list_item_category_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
app:layout_constraintTop_toBottomOf="@id/list_item_second_row_barrier"
|
||||
app:layout_constraintStart_toStartOf="@id/list_item_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Description" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -39,7 +39,7 @@ limitations under the License.
|
||||
android:paddingBottom="?attr/actionBarSize"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<ListView
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/track_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/activated_background"
|
||||
android:padding="8dp">
|
||||
|
||||
<!-- Icon -->
|
||||
<ImageView
|
||||
android:id="@+id/track_list_item_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/image_track"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_activity_bike_24dp" />
|
||||
|
||||
<!-- Name -->
|
||||
<TextView
|
||||
android:id="@+id/track_list_item_name"
|
||||
style="?attr/textAppearanceBody1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintStart_toEndOf="@id/track_list_item_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Main Text" />
|
||||
|
||||
<!-- Date -->
|
||||
<TextView
|
||||
android:id="@+id/track_list_item_date"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="2022-02-17" />
|
||||
|
||||
<!-- Total time and distance -->
|
||||
<TextView
|
||||
android:id="@+id/track_list_item_time_distance"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="@id/track_list_item_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/track_list_item_name"
|
||||
tools:text="50:43 (50.00km)" />
|
||||
|
||||
<!-- Marker Count Icon -->
|
||||
<ImageView
|
||||
android:id="@+id/track_list_item_marker_count_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/image_marker_count"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_marker_show_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/track_list_item_time_distance"
|
||||
app:layout_constraintStart_toEndOf="@id/track_list_item_time_distance"
|
||||
app:layout_constraintTop_toBottomOf="@id/track_list_item_name" />
|
||||
|
||||
<!-- Marker count -->
|
||||
<TextView
|
||||
android:id="@+id/track_list_item_marker_count"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/track_list_item_marker_count_icon"
|
||||
app:layout_constraintStart_toEndOf="@id/track_list_item_marker_count_icon"
|
||||
tools:text="2" />
|
||||
|
||||
<!-- Time -->
|
||||
<TextView
|
||||
android:id="@+id/track_list_item_time"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/track_list_item_name"
|
||||
tools:text="10:10:10" />
|
||||
|
||||
<!-- Second row barrier -->
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/track_list_item_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="track_list_item_time_distance,track_list_item_marker_count_icon,track_list_item_marker_count,track_list_item_time" />
|
||||
|
||||
<!-- Category and description -->
|
||||
<TextView
|
||||
android:id="@+id/track_list_item_category_description"
|
||||
style="?attr/textAppearanceBodySmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/track_list_item_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/track_list_item_barrier"
|
||||
tools:text="Description" />
|
||||
|
||||
<!-- Bottom Line -->
|
||||
<View
|
||||
android:id="@+id/marker_list_item_bottom_line"
|
||||
style="@style/HorizontalLine"
|
||||
android:layout_marginBottom="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/track_list_item_category_description" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Reference in New Issue
Block a user