mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 13:03:36 +00:00
[android] Show "Opens / Closes X at Y" using formatter + add i18n strings
- Wire `PlacePageView.refreshOpenState()` to `OpenStateTextFormatter`. - Keep <= 60 min branch with plurals (“Closes in %d minutes • at HH:mm”). - Add day hint when next change is not today (“Opens Sat at 09:00”). - Add localized strings with positional placeholders: - `opens_at` / `closes_at` (... `%s`). - `opens_day_at` / `closes_day_at` (`%1$s=%day`, `%2$s=%time`). Refs: #2303 Signed-off-by: NoelClick <dev@noel.click> (cherry picked from commit be80c7486882ab64a64efc30d0979d3674bbcc29) Signed-off-by: NoelClick <dev@noel.click>
This commit is contained in:
@@ -85,9 +85,11 @@ import com.google.android.material.textview.MaterialTextView;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.TextStyle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class PlacePageView extends Fragment
|
||||
implements View.OnClickListener, View.OnLongClickListener, LocationListener, SensorListener, Observer<MapObject>,
|
||||
@@ -797,57 +799,92 @@ public class PlacePageView extends Fragment
|
||||
final String ohStr = mMapObject.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS);
|
||||
final Timetable[] timetables = OpeningHours.nativeTimetablesFromString(ohStr);
|
||||
|
||||
if (timetables != null && timetables.length != 0)
|
||||
// No valid timetable
|
||||
if (timetables == null || timetables.length == 0)
|
||||
{
|
||||
final Context context = requireContext();
|
||||
final OhState poiState = OpeningHours.nativeCurrentState(timetables);
|
||||
|
||||
// Ignore unknown rule state
|
||||
if (poiState.state == OhState.State.Unknown)
|
||||
{
|
||||
UiUtils.hide(mTvOpenState);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get colours
|
||||
final ForegroundColorSpan colorGreen =
|
||||
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_green));
|
||||
final ForegroundColorSpan colorYellow =
|
||||
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_yellow));
|
||||
final ForegroundColorSpan colorRed = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_red));
|
||||
|
||||
// Get next state info
|
||||
final SpannableStringBuilder openStateString = new SpannableStringBuilder();
|
||||
final boolean isOpen = (poiState.state == OhState.State.Open); // False == Closed due to early exit for Unknown
|
||||
final long nextStateTime = isOpen ? poiState.nextTimeClosed : poiState.nextTimeOpen; // Unix time (seconds)
|
||||
final int minsToNextState = (int) ((nextStateTime - (System.currentTimeMillis() / 1000)) / 60);
|
||||
|
||||
if (minsToNextState <= 60) // POI opens/closes in 60 mins
|
||||
{
|
||||
final String minsToChangeStr = minsToNextState + " " + getString(R.string.minute);
|
||||
final String nextChangeFormatted = getString(isOpen ? R.string.closes_in : R.string.opens_in, minsToChangeStr);
|
||||
final ForegroundColorSpan nextChangeColor = isOpen ? colorYellow : colorRed;
|
||||
// TODO: We should check closed/open time for specific feature's timezone.
|
||||
ZonedDateTime time = ZonedDateTime.ofInstant(Instant.ofEpochSecond(nextStateTime), ZoneId.systemDefault());
|
||||
String localizedTime =
|
||||
new HoursMinutes(time.getHour(), time.getMinute(), DateUtils.is24HourFormat(context)).toString();
|
||||
|
||||
openStateString.append(nextChangeFormatted, nextChangeColor, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
.append(" • ") // Add spacer
|
||||
.append(getString(R.string.at, localizedTime));
|
||||
}
|
||||
else if (isOpen)
|
||||
openStateString.append(getString(R.string.open_now), colorGreen, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
// TODO: Add "Closes at 18:00" etc
|
||||
else // Closed
|
||||
openStateString.append(getString(R.string.closed_now), colorRed, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
// TODO: Add "Opens at 18:00" etc
|
||||
|
||||
UiUtils.setTextAndHideIfEmpty(mTvOpenState, openStateString);
|
||||
UiUtils.hide(mTvOpenState);
|
||||
return;
|
||||
}
|
||||
// No valid timetable
|
||||
UiUtils.hide(mTvOpenState);
|
||||
|
||||
final Context context = requireContext();
|
||||
final OhState poiState = OpeningHours.nativeCurrentState(timetables);
|
||||
|
||||
// Ignore unknown rule state
|
||||
if (poiState.state == OhState.State.Unknown)
|
||||
{
|
||||
UiUtils.hide(mTvOpenState);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get colours
|
||||
final ForegroundColorSpan colorGreen =
|
||||
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_green));
|
||||
final ForegroundColorSpan colorYellow =
|
||||
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_yellow));
|
||||
final ForegroundColorSpan colorRed = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_red));
|
||||
|
||||
// Get next state info
|
||||
final SpannableStringBuilder openStateString = new SpannableStringBuilder();
|
||||
final boolean isOpen = (poiState.state == OhState.State.Open); // False == Closed due to early exit for Unknown
|
||||
final long nextStateTime = isOpen ? poiState.nextTimeClosed : poiState.nextTimeOpen; // Unix time (seconds)
|
||||
final long nowSec = System.currentTimeMillis() / 1000;
|
||||
final int minsToNextState = (int) ((nextStateTime - nowSec) / 60);
|
||||
|
||||
// NOTE: Timezone is currently device timezone. TODO: use feature-specific timezone.
|
||||
final ZonedDateTime nextChangeLocal =
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochSecond(nextStateTime), ZoneId.systemDefault());
|
||||
|
||||
String localizedTimeString = OpenStateTextFormatter.formatHoursMinutes(
|
||||
nextChangeLocal.getHour(), nextChangeLocal.getMinute(), DateUtils.is24HourFormat(context));
|
||||
|
||||
if (minsToNextState <= 60 && minsToNextState >= 0) // POI Opens/Closes in 60 mins • at 18:00
|
||||
{
|
||||
final String minsToChangeStr = getResources().getQuantityString(
|
||||
R.plurals.minutes, Math.max(minsToNextState, 1), Math.max(minsToNextState, 1));
|
||||
final String nextChangeFormatted = getString(isOpen ? R.string.closes_in : R.string.opens_in, minsToChangeStr);
|
||||
final ForegroundColorSpan nextChangeColor = isOpen ? colorYellow : colorRed;
|
||||
|
||||
openStateString.append(nextChangeFormatted, nextChangeColor, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
.append(" • ") // Add spacer
|
||||
.append(getString(R.string.at, localizedTimeString));
|
||||
}
|
||||
else
|
||||
{
|
||||
final String opensAtStr = getString(R.string.opens_at); // "Opens at %s"
|
||||
final String closesAtStr = getString(R.string.closes_at); // "Closes at %s"
|
||||
final String opensDayAtStr = getString(R.string.opens_day_at); // "Opens %1$s at %2$s"
|
||||
final String closesDayAtStr = getString(R.string.closes_day_at); // "Closes %1$s at %2$s"
|
||||
|
||||
final boolean isToday =
|
||||
OpenStateTextFormatter.isSameLocalDate(nextChangeLocal, ZonedDateTime.now(nextChangeLocal.getZone()));
|
||||
final String dayShort =
|
||||
nextChangeLocal.getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.getDefault());
|
||||
|
||||
if (isOpen) // > 60 minutes OR negative (safety). Show “Open now • Closes at 18:00”
|
||||
{
|
||||
openStateString.append(getString(R.string.open_now), colorGreen, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
final String atLabel =
|
||||
OpenStateTextFormatter.buildAtLabel(false, isToday, dayShort, localizedTimeString,
|
||||
opensAtStr, closesAtStr, opensDayAtStr, closesDayAtStr);
|
||||
|
||||
if (!TextUtils.isEmpty(atLabel))
|
||||
openStateString.append(" • ").append(atLabel);
|
||||
}
|
||||
else // Closed
|
||||
{
|
||||
openStateString.append(getString(R.string.closed_now), colorRed, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
final String atLabel =
|
||||
OpenStateTextFormatter.buildAtLabel(true, isToday, dayShort, localizedTimeString,
|
||||
opensAtStr, closesAtStr, opensDayAtStr, closesDayAtStr);
|
||||
|
||||
if (!TextUtils.isEmpty(atLabel))
|
||||
openStateString.append(" • ").append(atLabel);
|
||||
}
|
||||
}
|
||||
|
||||
UiUtils.setTextAndHideIfEmpty(mTvOpenState, openStateString);
|
||||
}
|
||||
|
||||
private void addPlace()
|
||||
|
||||
@@ -441,6 +441,14 @@
|
||||
<string name="opens_in">Opens in %s</string>
|
||||
<string name="closes_in">Closes in %s</string>
|
||||
<string name="closed">Closed</string>
|
||||
<string name="opens_at">Opens at %s</string>
|
||||
<string name="closes_at">Closes at %s</string>
|
||||
<string name="opens_day_at">Opens %1$s at %2$s</string>
|
||||
<string name="closes_day_at">Closes %1$s at %2$s</string>
|
||||
<plurals name="minutes">
|
||||
<item quantity="one">%d minute</item>
|
||||
<item quantity="other">%d minutes</item>
|
||||
</plurals>
|
||||
<!-- Used in the opening_hours fragment for the last checked date, eg. "Confirmed two weeks ago" -->
|
||||
<string name="hours_confirmed_time_ago">Confirmed %s</string>
|
||||
<!-- Used on the place page for the last checked date, eg. "Existence confirmed two weeks ago" -->
|
||||
|
||||
Reference in New Issue
Block a user