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.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.TextStyle;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class PlacePageView extends Fragment
|
public class PlacePageView extends Fragment
|
||||||
implements View.OnClickListener, View.OnLongClickListener, LocationListener, SensorListener, Observer<MapObject>,
|
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 String ohStr = mMapObject.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS);
|
||||||
final Timetable[] timetables = OpeningHours.nativeTimetablesFromString(ohStr);
|
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();
|
UiUtils.hide(mTvOpenState);
|
||||||
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);
|
|
||||||
return;
|
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()
|
private void addPlace()
|
||||||
|
|||||||
@@ -441,6 +441,14 @@
|
|||||||
<string name="opens_in">Opens in %s</string>
|
<string name="opens_in">Opens in %s</string>
|
||||||
<string name="closes_in">Closes in %s</string>
|
<string name="closes_in">Closes in %s</string>
|
||||||
<string name="closed">Closed</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" -->
|
<!-- 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>
|
<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" -->
|
<!-- Used on the place page for the last checked date, eg. "Existence confirmed two weeks ago" -->
|
||||||
|
|||||||
Reference in New Issue
Block a user