mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 04:53:36 +00:00
[Android] Add current opening hours status to placepage preview
And refresh it every 45s Signed-off-by: Harry Bond <me@hbond.xyz> Signed-off-by: Harry Bond <me@hbond.xyz> Signed-off-by: Harry Bond <me@hbond.xyz> Signed-off-by: Harry Bond <me@hbond.xyz> Signed-off-by: Harry Bond <me@hbond.xyz> Signed-off-by: Harry Bond <me@hbond.xyz> Signed-off-by: Harry Bond <me@hbond.xyz> Signed-off-by: Harry Bond <me@hbond.xyz> Signed-off-by: Harry Bond <me@hbond.xyz>
This commit is contained in:
@@ -321,4 +321,30 @@ Java_app_organicmaps_editor_OpeningHours_nativeIsTimetableStringValid(JNIEnv * e
|
||||
{
|
||||
return OpeningHours(jni::ToNativeString(env, jSource)).IsValid();
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_app_organicmaps_editor_OpeningHours_nativeCurrentState(JNIEnv * env, jclass clazz, jobjectArray jTts)
|
||||
{
|
||||
TimeTableSet tts = NativeTimetableSet(env, jTts);
|
||||
time_t const now = time(nullptr);
|
||||
|
||||
/// @todo We should check closed/open time for specific feature's timezone.
|
||||
OpeningHours::InfoT ohInfo = MakeOpeningHours(tts).GetInfo(now);
|
||||
jclass ohStateClass = jni::GetGlobalClassRef(env, "app/organicmaps/editor/OhState");
|
||||
jclass ruleStateClass = jni::GetGlobalClassRef(env, "app/organicmaps/editor/OhState$State");
|
||||
|
||||
static const std::unordered_map<RuleState, const char*> ruleState = {
|
||||
{RuleState::Open, "Open"},
|
||||
{RuleState::Closed, "Closed"},
|
||||
{RuleState::Unknown, "Unknown"}
|
||||
};
|
||||
|
||||
jfieldID stateField = env->GetStaticFieldID(ruleStateClass, ruleState.at(ohInfo.state), "Lapp/organicmaps/editor/OhState$State;");
|
||||
jobject stateObj = env->GetStaticObjectField(ruleStateClass, stateField);
|
||||
jmethodID constructor = env->GetMethodID(ohStateClass, "<init>", "(Lapp/organicmaps/editor/OhState$State;JJ)V");
|
||||
jobject javaOhState = env->NewObject(ohStateClass, constructor, stateObj, (jlong) ohInfo.nextTimeOpen, (jlong) ohInfo.nextTimeClosed);
|
||||
|
||||
return javaOhState;
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package app.organicmaps.editor;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
// Used by JNI.
|
||||
@Keep
|
||||
public class OhState
|
||||
{
|
||||
public enum State
|
||||
{
|
||||
Open,
|
||||
Closed,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public State state;
|
||||
/** Unix timestamp in seconds**/
|
||||
public long nextTimeOpen;
|
||||
/** Unix timestamp in seconds **/
|
||||
public long nextTimeClosed;
|
||||
|
||||
// Used by JNI.
|
||||
@Keep
|
||||
public OhState(State state, long nextTimeOpen, long nextTimeClosed)
|
||||
{
|
||||
this.state = state;
|
||||
this.nextTimeOpen = nextTimeOpen;
|
||||
this.nextTimeClosed = nextTimeClosed;
|
||||
}
|
||||
}
|
||||
@@ -60,4 +60,6 @@ public final class OpeningHours
|
||||
* @return true if timetable string is valid OSM timetable.
|
||||
*/
|
||||
public static native boolean nativeIsTimetableStringValid(String source);
|
||||
|
||||
public static native OhState nativeCurrentState(@NonNull Timetable[] timetables);
|
||||
}
|
||||
|
||||
@@ -33,11 +33,16 @@ import app.organicmaps.downloader.CountryItem;
|
||||
import app.organicmaps.downloader.DownloaderStatusIcon;
|
||||
import app.organicmaps.downloader.MapManager;
|
||||
import app.organicmaps.editor.Editor;
|
||||
import app.organicmaps.editor.OhState;
|
||||
import app.organicmaps.editor.OpeningHours;
|
||||
import app.organicmaps.editor.data.HoursMinutes;
|
||||
import app.organicmaps.editor.data.Timetable;
|
||||
import app.organicmaps.location.LocationHelper;
|
||||
import app.organicmaps.location.LocationListener;
|
||||
import app.organicmaps.location.SensorHelper;
|
||||
import app.organicmaps.location.SensorListener;
|
||||
import app.organicmaps.routing.RoutingController;
|
||||
import app.organicmaps.util.DateUtils;
|
||||
import app.organicmaps.util.SharingUtils;
|
||||
import app.organicmaps.util.StringUtils;
|
||||
import app.organicmaps.util.UiUtils;
|
||||
@@ -54,6 +59,9 @@ import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -83,12 +91,15 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
|
||||
CoordinatesFormat.MGRS,
|
||||
CoordinatesFormat.OSMLink);
|
||||
private View mFrame;
|
||||
private Context mContext;
|
||||
|
||||
// Preview.
|
||||
private ViewGroup mPreview;
|
||||
private MaterialToolbar mToolbar;
|
||||
private MaterialTextView mTvTitle;
|
||||
private MaterialTextView mTvSecondaryTitle;
|
||||
private MaterialTextView mTvSubtitle;
|
||||
private MaterialTextView mTvOpenState;
|
||||
private ArrowView mAvDirection;
|
||||
private MaterialTextView mTvDistance;
|
||||
private MaterialTextView mTvAddress;
|
||||
@@ -201,7 +212,6 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
|
||||
|
||||
mFrame = view;
|
||||
mFrame.setOnClickListener((v) -> mPlacePageViewListener.onPlacePageRequestToggleState());
|
||||
|
||||
mPreview = mFrame.findViewById(R.id.pp__preview);
|
||||
|
||||
mFrame.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
@@ -220,6 +230,7 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
|
||||
mTvSecondaryTitle.setOnClickListener(this);
|
||||
mToolbar = mFrame.findViewById(R.id.toolbar);
|
||||
mTvSubtitle = mPreview.findViewById(R.id.tv__subtitle);
|
||||
mTvOpenState = mPreview.findViewById(R.id.tv__open_state);
|
||||
|
||||
View directionFrame = mPreview.findViewById(R.id.direction_frame);
|
||||
mTvDistance = mPreview.findViewById(R.id.tv__straight_distance);
|
||||
@@ -312,6 +323,7 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
|
||||
mViewModel.getMapObject().removeObserver(this);
|
||||
LocationHelper.from(requireContext()).removeListener(this);
|
||||
SensorHelper.from(requireContext()).removeListener(this);
|
||||
UiThread.cancelDelayedTasks(updateOpenState);
|
||||
detachCountry();
|
||||
}
|
||||
|
||||
@@ -412,6 +424,8 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
|
||||
{
|
||||
UiUtils.setTextAndHideIfEmpty(mTvTitle, mMapObject.getTitle());
|
||||
UiUtils.setTextAndHideIfEmpty(mTvSecondaryTitle, mMapObject.getSecondaryTitle());
|
||||
refreshOpenState();
|
||||
|
||||
if (mToolbar != null)
|
||||
mToolbar.setTitle(mMapObject.getTitle());
|
||||
setTextAndColorizeSubtitle();
|
||||
@@ -546,6 +560,62 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
|
||||
mTvLatlon.setText(latLon);
|
||||
}
|
||||
|
||||
Runnable updateOpenState = this::refreshOpenState;
|
||||
|
||||
private void refreshOpenState()
|
||||
{
|
||||
UiThread.runLater(updateOpenState, 45000); // Refresh every 45s
|
||||
|
||||
final String ohStr = mMapObject.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS);
|
||||
final Timetable[] timetables = OpeningHours.nativeTimetablesFromString(ohStr);
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
// No valid timetable
|
||||
UiUtils.hide(mTvOpenState);
|
||||
}
|
||||
|
||||
private void addOrganisation()
|
||||
{
|
||||
((MwmActivity) requireActivity()).showPositionChooserForEditor(true, false);
|
||||
@@ -556,9 +626,6 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
|
||||
((MwmActivity) requireActivity()).showPositionChooserForEditor(false, true);
|
||||
}
|
||||
|
||||
/// @todo
|
||||
/// - Why ll__place_editor and ll__place_latlon check if (mMapObject == null)
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
|
||||
@@ -71,6 +71,16 @@
|
||||
android:textAppearance="@style/MwmTextAppearance.Body3"
|
||||
tools:background="#300000F0"
|
||||
tools:text="Subtitle, very very very very very very very long" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tv__open_state"
|
||||
android:textAlignment="viewStart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_quarter"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/MwmTextAppearance.Body3"
|
||||
tools:background="#300000F0"
|
||||
tools:text="Open • Closing in 45 min" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tv__address"
|
||||
android:textAlignment="viewStart"
|
||||
|
||||
@@ -967,4 +967,7 @@
|
||||
<string name="ruler">Ruler</string>
|
||||
<string name="bookmark_color">Bookmark color</string>
|
||||
<string name="about_help">About & Help</string>
|
||||
<string name="open_now">Open now</string>
|
||||
<string name="closed_now">Closed now</string>
|
||||
<string name="at">at %s</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user