forked from upstream-mirrors/OpenTracks
Proper handling of timezone data on UI. Fixes #301.
Details: - Date and time handled by OffsetDateTime Java class. - Track default name with offset (for date local and ISO 8601 options). - TrackListActivity: shows relative today's date (like Today, Yesterday, 22 Dec, 22 Dec 2021, etc). - StatisticsRecordedFragment: always shows the original date and time. For example, if you record an activity at 20h then it always will show 20h regardless the zone are where you are. - Added new tests.
This commit is contained in:
committed by
Román Martínez
parent
1631836327
commit
5cd6992c90
@@ -27,6 +27,8 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
@@ -91,7 +93,7 @@ public class DescriptionGeneratorTest {
|
||||
+ "Min elevation: -500 m (-1640 ft)<br>"
|
||||
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||
+ "Elevation loss: 6000 m (19685 ft)<br>"
|
||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||
+ "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<br>";
|
||||
|
||||
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||
}
|
||||
@@ -127,7 +129,7 @@ public class DescriptionGeneratorTest {
|
||||
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
|
||||
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||
+ "Elevation loss: 6000 m (19685 ft)<br>"
|
||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||
+ "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<br>";
|
||||
|
||||
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -27,9 +29,23 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Month;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.TextStyle;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
@@ -184,4 +200,82 @@ public class StringUtilsTest {
|
||||
assertEquals("12.0 km/h", StringUtils.formatSpeed(context, Speed.of(3.34), true, true));
|
||||
assertEquals("7.5 mph", StringUtils.formatSpeed(context, Speed.of(3.34), false, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDateTime() {
|
||||
LocalDateTime localDateTime = LocalDateTime.parse("2022-01-02T10:15:30");
|
||||
|
||||
setLocale("en", "US");
|
||||
testFormatDateTime(localDateTime, localDateTime.getMonth().getDisplayName(TextStyle.SHORT, Locale.getDefault()) + " " + localDateTime.getDayOfMonth() + ", " + localDateTime.getYear() + " 10:15:30");
|
||||
|
||||
setLocale("es", "ES");
|
||||
testFormatDateTime(localDateTime, localDateTime.getDayOfMonth() + " " + localDateTime.getMonth().getDisplayName(TextStyle.SHORT, Locale.getDefault()) + " " + localDateTime.getYear() + " 10:15:30");
|
||||
|
||||
setLocale("de", "DE");
|
||||
testFormatDateTime(localDateTime, String.format("%02d", localDateTime.getDayOfMonth()) + "." + String.format("%02d", localDateTime.getMonthValue()) + "." + String.format("%04d", localDateTime.getYear()) + " 10:15:30");
|
||||
}
|
||||
|
||||
private void testFormatDateTime(LocalDateTime localDateTime, String expectedFormat) {
|
||||
// given
|
||||
int offsetFromLocale = OffsetDateTime.now().getOffset().getTotalSeconds() - 28800; // -08:00
|
||||
OffsetDateTime localOffsetDateTime = OffsetDateTime.of(localDateTime, ZoneOffset.systemDefault().getRules().getOffset(Instant.now()));
|
||||
OffsetDateTime outOffsetDateTime = OffsetDateTime.of(localDateTime, ZoneOffset.ofTotalSeconds(offsetFromLocale));
|
||||
|
||||
// when
|
||||
String formatDate = StringUtils.formatDateTime(outOffsetDateTime);
|
||||
|
||||
// then
|
||||
assertEquals(localOffsetDateTime.getOffset().getTotalSeconds(), outOffsetDateTime.getOffset().getTotalSeconds() + 28800);
|
||||
assertEquals(formatDate, expectedFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDateTodayRelative() {
|
||||
// given
|
||||
ArrayList<String> shortDays = Arrays.stream(DayOfWeek.values()).map(d -> d.getDisplayName(TextStyle.FULL, Locale.getDefault())).collect(Collectors.toCollection(ArrayList::new));
|
||||
ArrayList<String> shortMonths = Arrays.stream(Month.values()).map(m -> m.getDisplayName(TextStyle.SHORT, Locale.getDefault())).collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
LocalDate today = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).toLocalDate();
|
||||
LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
|
||||
LocalDate dayName = today.minus(2, ChronoUnit.DAYS);
|
||||
LocalDate thisYear = today.minus(15, ChronoUnit.DAYS);
|
||||
LocalDate aYearAgo = today.minus(400, ChronoUnit.DAYS);
|
||||
|
||||
int offsetFromLocale = OffsetDateTime.now().getOffset().getTotalSeconds() - 28800; // -08:00
|
||||
|
||||
OffsetDateTime todayRecordedOdt = OffsetDateTime.of(LocalDateTime.of(today.getYear(), today.getMonth(), today.getDayOfMonth(), 20, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
|
||||
OffsetDateTime yesterdayRecordedOdt = OffsetDateTime.of(LocalDateTime.of(yesterday.getYear(), yesterday.getMonth(), yesterday.getDayOfMonth(), 1, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
|
||||
OffsetDateTime dayNameRecordedOdt = OffsetDateTime.of(LocalDateTime.of(dayName.getYear(), dayName.getMonth(), dayName.getDayOfMonth(), 7, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
|
||||
OffsetDateTime thisYearRecordedOdt = OffsetDateTime.of(LocalDateTime.of(thisYear.getYear(), thisYear.getMonth(), thisYear.getDayOfMonth(), 12, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
|
||||
OffsetDateTime aYearAgoRecordedOdt = OffsetDateTime.of(LocalDateTime.of(aYearAgo.getYear(), aYearAgo.getMonth(), aYearAgo.getDayOfMonth(), 17, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
|
||||
|
||||
// when
|
||||
String formatToday = StringUtils.formatDateTodayRelative(context, todayRecordedOdt);
|
||||
String formatYesterday = StringUtils.formatDateTodayRelative(context, yesterdayRecordedOdt);
|
||||
String formatDayName = StringUtils.formatDateTodayRelative(context, dayNameRecordedOdt);
|
||||
String formatThisYear = StringUtils.formatDateTodayRelative(context, thisYearRecordedOdt);
|
||||
String formatAYearAgo = StringUtils.formatDateTodayRelative(context, aYearAgoRecordedOdt);
|
||||
|
||||
// then
|
||||
assertEquals("Today", formatToday);
|
||||
assertEquals("Yesterday", formatYesterday);
|
||||
assertTrue(shortDays.contains(formatDayName)); // Something like Friday
|
||||
if (today.getYear() != thisYear.getYear()) {
|
||||
assertTrue(shortMonths.stream().anyMatch(fty -> formatThisYear.matches("\\d+ " + fty + " \\d{4}"))); // Something like 14 Dec 2021
|
||||
} else {
|
||||
assertTrue(shortMonths.stream().anyMatch(fty -> formatThisYear.matches("\\d+ " + fty))); // Something like 14 Dec
|
||||
}
|
||||
assertTrue(shortMonths.stream().anyMatch(fty -> formatAYearAgo.matches("\\d+ " + fty + " \\d{4}"))); // Something like 14 Dec 2021
|
||||
}
|
||||
|
||||
private void setLocale(String language, String country) {
|
||||
Locale locale = new Locale(language, country);
|
||||
// here we update locale for date formatters
|
||||
Locale.setDefault(locale);
|
||||
// here we update locale for app resources
|
||||
Resources res = context.getResources();
|
||||
Configuration config = res.getConfiguration();
|
||||
config.locale = locale;
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -24,16 +26,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.util.Locale;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests {@link TrackNameUtils}.
|
||||
*
|
||||
@@ -43,7 +42,7 @@ import static org.junit.Assert.assertEquals;
|
||||
public class TrackNameUtilsTest {
|
||||
|
||||
private static final Track.Id TRACK_ID = new Track.Id(1L);
|
||||
private static final Instant START_TIME = Instant.ofEpochMilli(1288213406000L);
|
||||
private static final OffsetDateTime START_TIME = OffsetDateTime.parse("2022-01-02T10:15:30+01:00");
|
||||
|
||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@@ -53,7 +52,7 @@ public class TrackNameUtilsTest {
|
||||
@Test
|
||||
public void testTrackName_date_local() {
|
||||
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
||||
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
assertEquals(StringUtils.formatDateTimeWithOffset(START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,8 +61,7 @@ public class TrackNameUtilsTest {
|
||||
@Test
|
||||
public void testTrackName_date_iso_8601() {
|
||||
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
|
||||
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
assertEquals(StringUtils.formatDateTimeIso8601(START_TIME.toInstant(), START_TIME.getOffset()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -326,7 +326,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||
final String[] PROJECTION = new String[]{MarkerColumns._ID,
|
||||
MarkerColumns.NAME, MarkerColumns.DESCRIPTION, MarkerColumns.CATEGORY,
|
||||
MarkerColumns.TIME, MarkerColumns.PHOTOURL};
|
||||
MarkerColumns.TIME, MarkerColumns.PHOTOURL, MarkerColumns.TRACKID};
|
||||
|
||||
if (searchQuery == null) {
|
||||
if (track != null) {
|
||||
|
||||
@@ -49,6 +49,9 @@ import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -190,6 +193,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
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 categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
||||
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
||||
int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
|
||||
@@ -203,12 +207,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, Distance.of(cursor.getDouble(totalDistanceIndex)), metricUnits);
|
||||
int markerCount = cursor.getInt(markerCountIndex);
|
||||
long startTime = cursor.getLong(startTimeIndex);
|
||||
int startTimeOffset = cursor.getInt(startTimeOffsetIndex);
|
||||
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
|
||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingStatus.isPaused(),
|
||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||
startTime, true, category, description, false);
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneOffset.ofTotalSeconds(startTimeOffset)),
|
||||
category, description, false);
|
||||
}
|
||||
};
|
||||
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
||||
@@ -515,7 +521,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||
final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
|
||||
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
|
||||
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME, TracksColumns.STARTTIME_OFFSET,
|
||||
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON, TracksColumns.MARKER_COUNT};
|
||||
|
||||
final String sortOrder = TracksColumns.STARTTIME + " DESC";
|
||||
|
||||
@@ -19,9 +19,15 @@ import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
|
||||
@@ -66,6 +72,7 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
|
||||
int categoryIndex = cursor.getColumnIndex(MarkerColumns.CATEGORY);
|
||||
int descriptionIndex = cursor.getColumnIndex(MarkerColumns.DESCRIPTION);
|
||||
int photoUrlIndex = cursor.getColumnIndex(MarkerColumns.PHOTOURL);
|
||||
int trackIdIndex = cursor.getColumnIndex(MarkerColumns.TRACKID);
|
||||
|
||||
long id = cursor.getLong(idIndex);
|
||||
int iconId = MarkerUtils.ICON_ID;
|
||||
@@ -74,6 +81,7 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
|
||||
String category = cursor.getString(categoryIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
String photoUrl = cursor.getString(photoUrlIndex);
|
||||
long trackId = cursor.getLong(trackIdIndex);
|
||||
|
||||
view.setTag(String.valueOf(id));
|
||||
|
||||
@@ -96,7 +104,10 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
|
||||
}
|
||||
}
|
||||
|
||||
ListItemUtils.setListItem(activity, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, hasPhoto);
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
Track track = contentProviderUtils.getTrack(new Track.Id(trackId));
|
||||
ListItemUtils.setListItem(activity, view, false, true, iconId, R.string.image_marker, name, null, null, 0,
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochMilli(time), track.getZoneOffset()), category, description, hasPhoto);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.util.Pair;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
@@ -150,7 +152,7 @@ public class DescriptionGenerator {
|
||||
}
|
||||
|
||||
// Recorded time
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(OffsetDateTime.ofInstant(stats.getStartTime(), ZoneId.systemDefault()))));
|
||||
builder.append(lineBreak);
|
||||
|
||||
return builder.toString();
|
||||
|
||||
@@ -82,10 +82,10 @@ public class FilterDialogFragment extends DialogFragment {
|
||||
TextInputEditText dateTo = layout.findViewById(R.id.filter_date_edit_text_to);
|
||||
|
||||
LocalDateTime firstDayThisWeek = LocalDate.now().with(WeekFields.of(Locale.getDefault()).getFirstDayOfWeek()).atStartOfDay();
|
||||
dateFrom.setText(StringUtils.formatDate(getActivity(), firstDayThisWeek));
|
||||
dateFrom.setText(StringUtils.formatLocalDateTime(firstDayThisWeek));
|
||||
datePickerFrom.init(firstDayThisWeek.getYear(), firstDayThisWeek.getMonthValue() - 1, firstDayThisWeek.getDayOfMonth(), (view, year, monthOfYear, dayOfMonth) -> {
|
||||
LocalDateTime localDateTime = LocalDateTime.of(year, monthOfYear + 1, dayOfMonth, 0, 0, 0);
|
||||
dateFrom.setText(StringUtils.formatDate(getActivity(), localDateTime));
|
||||
dateFrom.setText(StringUtils.formatLocalDateTime(localDateTime));
|
||||
datePickerFrom.setVisibility(View.GONE);
|
||||
datePickerTo.setMinDate(localDateTime.toInstant(ZoneOffset.ofTotalSeconds(0)).toEpochMilli());
|
||||
if (localDateTime.isAfter(LocalDateTime.of(datePickerTo.getYear(), datePickerTo.getMonth() + 1, datePickerTo.getDayOfMonth(), 23, 59, 59))) {
|
||||
@@ -94,10 +94,10 @@ public class FilterDialogFragment extends DialogFragment {
|
||||
});
|
||||
|
||||
LocalDateTime lastDayThisWeek = firstDayThisWeek.plusDays(6).withHour(23).withMinute(59).withSecond(59);
|
||||
dateTo.setText(StringUtils.formatDate(getActivity(), lastDayThisWeek));
|
||||
dateTo.setText(StringUtils.formatLocalDateTime(lastDayThisWeek));
|
||||
datePickerTo.init(lastDayThisWeek.getYear(), lastDayThisWeek.getMonthValue() - 1, lastDayThisWeek.getDayOfMonth(), (view, year, monthOfYear, dayOfMonth) -> {
|
||||
LocalDateTime localDateTime = LocalDateTime.of(year, monthOfYear + 1, dayOfMonth, 23, 59, 59);
|
||||
dateTo.setText(StringUtils.formatDate(getActivity(), localDateTime));
|
||||
dateTo.setText(StringUtils.formatLocalDateTime(localDateTime));
|
||||
datePickerTo.setVisibility(View.GONE);
|
||||
});
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
private void loadTrackDescription(@NonNull Track track) {
|
||||
viewBinding.statsNameValue.setText(track.getName());
|
||||
viewBinding.statsDescriptionValue.setText(track.getDescription());
|
||||
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(getContext(), track.getTrackStatistics().getStartTime()));
|
||||
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(track.getStartTime()));
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -59,13 +60,12 @@ class TrackRecordingManager {
|
||||
|
||||
insertTrackPoint(trackId, segmentStartTrackPoint);
|
||||
|
||||
//TODO Pass TrackPoint
|
||||
track.setName(TrackNameUtils.getTrackName(context, trackId, segmentStartTrackPoint.getTime()));
|
||||
|
||||
String category = PreferencesUtils.getDefaultActivity();
|
||||
track.setCategory(category);
|
||||
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
//TODO Pass TrackPoint
|
||||
track.setName(TrackNameUtils.getTrackName(context, trackId, track.getStartTime()));
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
currentSegmentHasTrackPoint = false;
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
@@ -26,6 +24,9 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
/**
|
||||
@@ -51,13 +52,12 @@ public class ListItemUtils {
|
||||
* @param totalTime the total time value
|
||||
* @param totalDistance the total distance value
|
||||
* @param markerCount the marker count
|
||||
* @param startTime the start time value
|
||||
* @param useRelativeTime true to display relative time if appropriate
|
||||
* @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, boolean isPaused, int iconId, int iconContentDescriptionId, String name, String totalTime, String totalDistance, int markerCount, long startTime, boolean useRelativeTime, String category, String description, boolean hasPhoto) {
|
||||
public static void setListItem(Context context, View view, boolean isRecording, boolean isPaused, 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 = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording;
|
||||
@@ -104,12 +104,16 @@ public class ListItemUtils {
|
||||
setTextView(context, markerCountTextView, markerCountValue, hasPhoto);
|
||||
|
||||
// Set date/time
|
||||
String[] dateTime = getDateTime(isRecording, context, startTime, useRelativeTime);
|
||||
TextView dateTextView = view.findViewById(R.id.list_item_date);
|
||||
setTextView(context, dateTextView, dateTime[0], hasPhoto);
|
||||
|
||||
TextView timeTextView = view.findViewById(R.id.list_item_time);
|
||||
setTextView(context, timeTextView, dateTime[1], hasPhoto);
|
||||
String dateValue = null;
|
||||
String timeValue = null;
|
||||
if (!isRecording) {
|
||||
dateValue = StringUtils.formatDateTodayRelative(context, offsetDateTime);
|
||||
timeValue = offsetDateTime.format(DateTimeFormatter.ofPattern("hh:mm"));
|
||||
}
|
||||
setTextView(context, dateTextView, dateValue, hasPhoto);
|
||||
setTextView(context, timeTextView, timeValue, hasPhoto);
|
||||
|
||||
// Set category and description
|
||||
TextView categoryDescriptionTextView = view.findViewById(R.id.list_item_category_description);
|
||||
@@ -133,27 +137,6 @@ public class ListItemUtils {
|
||||
params.gravity = timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL;
|
||||
}
|
||||
|
||||
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
|
||||
// Raw height and width of image
|
||||
final int height = options.outHeight;
|
||||
final int width = options.outWidth;
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width larger than the requested height and width.
|
||||
while ((halfHeight / inSampleSize) >= reqHeight
|
||||
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string for share owner, total time, and total distance.
|
||||
*
|
||||
@@ -177,30 +160,6 @@ public class ListItemUtils {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date and time as an array of two strings.
|
||||
*
|
||||
* @param isRecording true if recording
|
||||
* @param context the context
|
||||
* @param time the start time
|
||||
*/
|
||||
private static String[] getDateTime(boolean isRecording, Context context, long time, boolean useRelativeTime) {
|
||||
if (isRecording || time == 0L) {
|
||||
return new String[]{null, null};
|
||||
}
|
||||
|
||||
boolean isToday = DateUtils.isToday(time);
|
||||
int timeFlags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL;
|
||||
|
||||
if (isToday && useRelativeTime) {
|
||||
return new String[]{DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString(), null};
|
||||
} else if (isToday) {
|
||||
return new String[]{DateUtils.formatDateTime(context, time, timeFlags), null};
|
||||
}
|
||||
|
||||
return new String[]{DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL), DateUtils.formatDateTime(context, time, timeFlags)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a text view.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.DAYS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.text.TextUtils;
|
||||
@@ -28,12 +30,14 @@ import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -54,18 +58,47 @@ public class StringUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the date and time based on user's phone date/time preferences.
|
||||
* Formats the date and time of the OffsetDateTime (using default Locale format)
|
||||
*/
|
||||
public static String formatDateTime(Context context, Instant time) {
|
||||
return DateUtils.formatDateTime(context, time.toEpochMilli(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE)
|
||||
+ " " + DateUtils.formatDateTime(context, time.toEpochMilli(), DateUtils.FORMAT_SHOW_TIME);
|
||||
public static String formatDateTime(OffsetDateTime odt) {
|
||||
return odt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the date and time based on user's phone date/time preferences.
|
||||
* Formats the date and time with the offset (using default Locale format).
|
||||
*/
|
||||
public static String formatDate(Context context, LocalDateTime localDateTime) {
|
||||
return DateUtils.formatDateTime(context, localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(), DateUtils.FORMAT_SHOW_DATE);
|
||||
public static String formatDateTimeWithOffset(OffsetDateTime odt) {
|
||||
return odt.toZonedDateTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL));
|
||||
}
|
||||
|
||||
public static String formatLocalDateTime(LocalDateTime localDateTime) {
|
||||
return localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the date relative to today date.
|
||||
*/
|
||||
public static String formatDateTodayRelative(Context context, OffsetDateTime odt) {
|
||||
LocalDate today = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).toLocalDate();
|
||||
LocalDate ld = odt.toLocalDate();
|
||||
long daysBetween = DAYS.between(ld, today);
|
||||
|
||||
if (daysBetween == 0) {
|
||||
// Today
|
||||
return context.getString(R.string.generic_today);
|
||||
} else if (daysBetween == 1) {
|
||||
// Yesterday
|
||||
return context.getString(R.string.generic_yesterday);
|
||||
} else if (daysBetween < 7) {
|
||||
// Name of the week day
|
||||
return ld.format(DateTimeFormatter.ofPattern("EEEE"));
|
||||
} else if (today.getYear() == ld.getYear()) {
|
||||
// Short date without year
|
||||
return ld.format(DateTimeFormatter.ofPattern("d MMM"));
|
||||
} else {
|
||||
// Short date with year
|
||||
return ld.format(DateTimeFormatter.ofPattern("d MMM y"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,11 +18,7 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.util.Locale;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -35,21 +31,17 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
*/
|
||||
public class TrackNameUtils {
|
||||
|
||||
//TODO Could be available in java.time?
|
||||
@VisibleForTesting
|
||||
static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm";
|
||||
|
||||
private TrackNameUtils() {
|
||||
}
|
||||
|
||||
//TODO Should not access sharedPreferences; trackName should be an ENUM.
|
||||
public static String getTrackName(Context context, Track.Id trackId, Instant startTime) {
|
||||
public static String getTrackName(Context context, Track.Id trackId, OffsetDateTime startTime) {
|
||||
String trackName = PreferencesUtils.getString(R.string.track_name_key, context.getString(R.string.track_name_default));
|
||||
|
||||
if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
|
||||
return StringUtils.formatDateTime(context, startTime);
|
||||
return StringUtils.formatDateTimeWithOffset(startTime);
|
||||
} else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
|
||||
return new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime.toEpochMilli());
|
||||
return StringUtils.formatDateTimeIso8601(startTime.toInstant(), startTime.getOffset());
|
||||
} else {
|
||||
return context.getString(R.string.track_name_format, trackId.getId());
|
||||
}
|
||||
|
||||
@@ -244,6 +244,8 @@ limitations under the License.
|
||||
<string name="generic_filter">Filter</string>
|
||||
<string name="generic_from">From</string>
|
||||
<string name="generic_to">To</string>
|
||||
<string name="generic_today">Today</string>
|
||||
<string name="generic_yesterday">Yesterday</string>
|
||||
<!-- Gps -->
|
||||
<string name="gps_starting">Starting GPS</string>
|
||||
<string name="gps_wait_for_better_signal">Waiting for a better GPS signal</string>
|
||||
|
||||
Reference in New Issue
Block a user