mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 13:03:36 +00:00
[android] add support for editing charging stations details
While here, uncluttered a little the charging station editor, removing fields like the POI address which is basically irrelevant for charging stations. Signed-off-by: Séverin Lemaignan <severin@guakamole.org>
This commit is contained in:
@@ -1,13 +1,19 @@
|
||||
package app.organicmaps.editor;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.GridLayout;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IdRes;
|
||||
@@ -22,6 +28,7 @@ import app.organicmaps.base.BaseMwmFragment;
|
||||
import app.organicmaps.dialog.EditTextDialogFragment;
|
||||
import app.organicmaps.editor.data.TimeFormatUtils;
|
||||
import app.organicmaps.sdk.Framework;
|
||||
import app.organicmaps.sdk.bookmarks.data.ChargeSocketDescriptor;
|
||||
import app.organicmaps.sdk.bookmarks.data.Metadata;
|
||||
import app.organicmaps.sdk.editor.Editor;
|
||||
import app.organicmaps.sdk.editor.OpeningHours;
|
||||
@@ -30,26 +37,33 @@ import app.organicmaps.sdk.editor.data.LocalizedStreet;
|
||||
import app.organicmaps.sdk.editor.data.Timetable;
|
||||
import app.organicmaps.sdk.util.StringUtils;
|
||||
import app.organicmaps.sdk.util.Utils;
|
||||
import app.organicmaps.sdk.util.log.Logger;
|
||||
import app.organicmaps.util.Graphics;
|
||||
import app.organicmaps.util.InputUtils;
|
||||
import app.organicmaps.util.UiUtils;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class EditorFragment extends BaseMwmFragment implements View.OnClickListener
|
||||
{
|
||||
final static String LAST_INDEX_OF_NAMES_ARRAY = "LastIndexOfNamesArray";
|
||||
private static final String CHARGE_SOCKETS_TAG = "CHARGE_SOCKETS_TAG";
|
||||
|
||||
private MaterialTextView mCategory;
|
||||
private View mCardName;
|
||||
private View mCardAddress;
|
||||
private View mCardChargingStation;
|
||||
private View mCardDetails;
|
||||
private View mCardSocialMedia;
|
||||
private View mCardBuilding;
|
||||
@@ -130,6 +144,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
private TextInputLayout mInputHouseNumber;
|
||||
private TextInputLayout mInputBuildingLevels;
|
||||
|
||||
private View mChargeSockets;
|
||||
|
||||
private View mEmptyOpeningHours;
|
||||
private MaterialTextView mOpeningHours;
|
||||
private View mEditOpeningHours;
|
||||
@@ -206,6 +222,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
mWifi.setChecked(Editor.nativeHasWifi());
|
||||
// TODO Reimplement this to avoid https://github.com/organicmaps/organicmaps/issues/9049
|
||||
// mOutdoorSeating.setChecked(Editor.nativeGetSwitchInput(Metadata.MetadataType.FMD_OUTDOOR_SEATING.toInt(),"yes"));
|
||||
refreshChargeSockets();
|
||||
refreshOpeningTime();
|
||||
refreshEditableFields();
|
||||
refreshResetButton();
|
||||
@@ -329,6 +346,14 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
|
||||
final int[] editableDetails = Editor.nativeGetEditableProperties();
|
||||
|
||||
// charge sockets have their own card; check whether we should display it.
|
||||
boolean hasChargeSockets = false;
|
||||
for (int type : editableDetails)
|
||||
{
|
||||
hasChargeSockets = hasChargeSockets || (type == Metadata.MetadataType.FMD_CHARGE_SOCKETS.toInt());
|
||||
}
|
||||
UiUtils.showIf(hasChargeSockets, mCardChargingStation);
|
||||
|
||||
setCardVisibility(mCardDetails, mDetailsBlocks, editableDetails);
|
||||
setCardVisibility(mCardSocialMedia, mSocialMediaBlocks, editableDetails);
|
||||
}
|
||||
@@ -351,6 +376,283 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
UiUtils.showIf(anyBlockElement, card);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a dialog for editing or adding a charge socket.
|
||||
*
|
||||
* @param socketIndex The index of the socket to edit, or -1 to add a new socket.
|
||||
* @param type The current type of the socket (e.g., "type2", "type2_combo").
|
||||
* @param count The current number of sockets of this type or 0 for new socket.
|
||||
* @param power The current power output of the socket in kW or 0 for new socket.
|
||||
* @return A MaterialAlertDialogBuilder instance for the configured dialog.
|
||||
*/
|
||||
private MaterialAlertDialogBuilder buildChargeSocketDialog(int socketIndex, String type, int count, double power)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(getActivity());
|
||||
View dialogView = inflater.inflate(R.layout.dialog_edit_socket, null);
|
||||
|
||||
GridLayout typeBtns = dialogView.findViewById(R.id.edit_socket_type_grid);
|
||||
typeBtns.removeAllViews();
|
||||
|
||||
List<String> SOCKET_TYPES = Arrays.stream(getResources().getStringArray(R.array.charge_socket_types)).toList();
|
||||
for (String socket : SOCKET_TYPES)
|
||||
{
|
||||
MaterialButton btn = (MaterialButton) inflater.inflate(R.layout.button_socket_type, typeBtns, false);
|
||||
|
||||
btn.setTag(R.id.socket_type, socket);
|
||||
|
||||
// load SVG icon converted into VectorDrawable in res/drawable
|
||||
@SuppressLint("DiscouragedApi")
|
||||
int resIconId =
|
||||
getResources().getIdentifier("ic_charge_socket_" + socket, "drawable", requireContext().getPackageName());
|
||||
if (resIconId != 0)
|
||||
{
|
||||
btn.setIcon(getResources().getDrawable(resIconId));
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
int resTypeId =
|
||||
getResources().getIdentifier("charge_socket_" + socket, "string", requireContext().getPackageName());
|
||||
if (resTypeId != 0)
|
||||
{
|
||||
btn.setText(getResources().getString(resTypeId));
|
||||
}
|
||||
|
||||
if (socket.equals(type))
|
||||
{
|
||||
btn.setChecked(true);
|
||||
}
|
||||
|
||||
typeBtns.addView(btn);
|
||||
}
|
||||
|
||||
// manage the grid of socket type buttons as a single 'radio group'
|
||||
// (this can not be done with a MaterialButtonToggleGroup because it does
|
||||
// not support GridLayout)
|
||||
List<MaterialButton> buttonList = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < typeBtns.getChildCount(); i++)
|
||||
{
|
||||
View child = typeBtns.getChildAt(i);
|
||||
if (child instanceof MaterialButton button)
|
||||
{
|
||||
buttonList.add(button);
|
||||
|
||||
button.setOnClickListener(view -> {
|
||||
// deselect all
|
||||
for (MaterialButton b : buttonList)
|
||||
{
|
||||
b.setChecked(false);
|
||||
}
|
||||
// select clicked
|
||||
button.setChecked(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TextInputLayout countInputLayout = dialogView.findViewById(R.id.edit_socket_count_layout);
|
||||
AutoCompleteTextView countView = dialogView.findViewById(R.id.edit_socket_count);
|
||||
if (count > 0)
|
||||
{
|
||||
countView.setText(String.valueOf(count));
|
||||
}
|
||||
|
||||
// Add a TextWatcher to validate on text change
|
||||
countView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
validatePositiveField(s.toString(), countInputLayout);
|
||||
}
|
||||
});
|
||||
|
||||
TextInputLayout powerInputLayout = dialogView.findViewById(R.id.edit_socket_power_layout);
|
||||
AutoCompleteTextView powerView = dialogView.findViewById(R.id.edit_socket_power);
|
||||
if (power > 0)
|
||||
{
|
||||
powerView.setText(String.valueOf(power));
|
||||
}
|
||||
|
||||
// Add a TextWatcher to validate on text change
|
||||
powerView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
validatePositiveField(s.toString(), powerInputLayout);
|
||||
}
|
||||
});
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
|
||||
.setTitle(R.string.editor_socket)
|
||||
.setView(dialogView)
|
||||
.setPositiveButton(R.string.save,
|
||||
(dialog, which) -> {
|
||||
String socketType = "";
|
||||
for (MaterialButton b : buttonList)
|
||||
{
|
||||
if (b.isChecked())
|
||||
{
|
||||
socketType = b.getTag(R.id.socket_type).toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int countValue = 0; // 0 means 'unknown count'
|
||||
try
|
||||
{
|
||||
countValue = Integer.parseInt(countView.getText().toString());
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString());
|
||||
}
|
||||
|
||||
if (countValue < 0)
|
||||
{
|
||||
countValue = 0;
|
||||
Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString());
|
||||
}
|
||||
|
||||
double powerValue = 0; // 0 means 'unknown power'
|
||||
try
|
||||
{
|
||||
powerValue = Double.parseDouble(powerView.getText().toString());
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString());
|
||||
}
|
||||
|
||||
if (powerValue < 0)
|
||||
{
|
||||
powerValue = 0;
|
||||
Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString());
|
||||
}
|
||||
|
||||
ChargeSocketDescriptor socket =
|
||||
new ChargeSocketDescriptor(socketType, countValue, powerValue);
|
||||
|
||||
updateChargeSockets(socketIndex, socket);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> { dialog.dismiss(); });
|
||||
}
|
||||
|
||||
// Helper method for validation logic
|
||||
private boolean validatePositiveField(String text, TextInputLayout layout) {
|
||||
if (text.isEmpty()) {
|
||||
layout.setError(null); // No error if empty (assuming 0 is the default)
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
double value = Double.parseDouble(text);
|
||||
if (value < 0) {
|
||||
layout.setError(getString(R.string.error_value_must_be_positive));
|
||||
return false;
|
||||
} else {
|
||||
layout.setError(null);
|
||||
return true;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
layout.setError(getString(R.string.error_invalid_number));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updates the list of charge sockets.
|
||||
* If socketIndex is >=0, it updates the socket at that index.
|
||||
* Otherwise, it adds the new socket to the list.
|
||||
*
|
||||
* @param socketIndex The index of the socket to update, or -1 to add a new socket.
|
||||
* @param socket The ChargeSocketDescriptor of the socket to add or update.
|
||||
*/
|
||||
private void updateChargeSockets(int socketIndex, ChargeSocketDescriptor socket)
|
||||
{
|
||||
ChargeSocketDescriptor[] sockets = Editor.nativeGetChargeSockets();
|
||||
if (socketIndex >= 0)
|
||||
{
|
||||
sockets[socketIndex] = socket;
|
||||
}
|
||||
else {
|
||||
List<ChargeSocketDescriptor> list = new ArrayList<>(Arrays.asList(sockets));
|
||||
list.add(socket);
|
||||
sockets = list.toArray(new ChargeSocketDescriptor[0]);
|
||||
}
|
||||
Editor.nativeSetChargeSockets(sockets);
|
||||
|
||||
refreshChargeSockets();
|
||||
}
|
||||
private void refreshChargeSockets()
|
||||
{
|
||||
ChargeSocketDescriptor[] sockets = Editor.nativeGetChargeSockets();
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(requireContext());
|
||||
|
||||
GridLayout socketsGrid = mChargeSockets.findViewById(R.id.socket_grid_editor);
|
||||
socketsGrid.removeAllViews();
|
||||
|
||||
for (int i = 0; i < sockets.length; i++)
|
||||
{
|
||||
final int currentIndex = i;
|
||||
ChargeSocketDescriptor socket = sockets[i];
|
||||
|
||||
View itemView = inflater.inflate(R.layout.item_charge_socket, socketsGrid, false);
|
||||
|
||||
MaterialTextView type = itemView.findViewById(R.id.socket_type);
|
||||
ShapeableImageView icon = itemView.findViewById(R.id.socket_icon);
|
||||
MaterialTextView power = itemView.findViewById(R.id.socket_power);
|
||||
MaterialTextView count = itemView.findViewById(R.id.socket_count);
|
||||
|
||||
// load SVG icon converted into VectorDrawable in res/drawable
|
||||
@SuppressLint("DiscouragedApi")
|
||||
int resIconId = getResources().getIdentifier("ic_charge_socket_" + socket.type(), "drawable",
|
||||
requireContext().getPackageName());
|
||||
if (resIconId != 0)
|
||||
{
|
||||
icon.setImageResource(resIconId);
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
int resTypeId =
|
||||
getResources().getIdentifier("charge_socket_" + socket.type(), "string", requireContext().getPackageName());
|
||||
if (resTypeId != 0)
|
||||
{
|
||||
type.setText(resTypeId);
|
||||
}
|
||||
|
||||
if (socket.power() != 0)
|
||||
{
|
||||
DecimalFormat df = new DecimalFormat("#.##");
|
||||
power.setText(getString(R.string.kw_label, df.format(socket.power())));
|
||||
}
|
||||
|
||||
if (socket.count() != 0)
|
||||
{
|
||||
count.setText(getString(R.string.count_label, socket.count()));
|
||||
}
|
||||
|
||||
itemView.setOnClickListener(v -> {
|
||||
buildChargeSocketDialog(currentIndex, socket.type(), socket.count(), socket.power()).show();
|
||||
});
|
||||
socketsGrid.addView(itemView);
|
||||
}
|
||||
|
||||
// add a 'new item' button at the end, to create new sockets
|
||||
View btnNewItemView = inflater.inflate(R.layout.button_new_item, socketsGrid, false);
|
||||
btnNewItemView.setOnClickListener(v -> {
|
||||
buildChargeSocketDialog(-1, "unknown", -1, -1).show();
|
||||
});
|
||||
socketsGrid.addView(btnNewItemView);
|
||||
}
|
||||
|
||||
private void refreshOpeningTime()
|
||||
{
|
||||
final String openingHours = Editor.nativeGetOpeningHours();
|
||||
@@ -435,6 +737,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
mCategory = categoryBlock.findViewById(R.id.name);
|
||||
mCardName = view.findViewById(R.id.cv__name);
|
||||
mCardAddress = view.findViewById(R.id.cv__address);
|
||||
mCardChargingStation = view.findViewById(R.id.cv__charging_station);
|
||||
mCardDetails = view.findViewById(R.id.cv__details);
|
||||
mCardSocialMedia = view.findViewById(R.id.cv__social_media);
|
||||
mCardBuilding = view.findViewById(R.id.cv__building);
|
||||
@@ -507,6 +810,9 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
|
||||
View blockOutdoorSeating = view.findViewById(R.id.block_outdoor_seating);
|
||||
mOutdoorSeating = view.findViewById(R.id.sw__outdoor_seating);
|
||||
blockOutdoorSeating.setOnClickListener(this);
|
||||
|
||||
mChargeSockets = view.findViewById(R.id.block_charge_sockets);
|
||||
|
||||
View blockOpeningHours = view.findViewById(R.id.block_opening_hours);
|
||||
mEditOpeningHours = blockOpeningHours.findViewById(R.id.edit_opening_hours);
|
||||
mEditOpeningHours.setOnClickListener(this);
|
||||
|
||||
@@ -79,6 +79,9 @@ public class PlacePageChargeSocketsFragment extends Fragment implements Observer
|
||||
{
|
||||
View itemView = inflater.inflate(R.layout.item_charge_socket, mGrid, false);
|
||||
|
||||
itemView.setClickable(false);
|
||||
itemView.setFocusable(false);
|
||||
|
||||
MaterialTextView type = itemView.findViewById(R.id.socket_type);
|
||||
ShapeableImageView icon = itemView.findViewById(R.id.socket_icon);
|
||||
MaterialTextView power = itemView.findViewById(R.id.socket_power);
|
||||
|
||||
72
android/app/src/main/res/layout-land/dialog_edit_socket.xml
Normal file
72
android/app/src/main/res/layout-land/dialog_edit_socket.xml
Normal file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="20dp"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<!-- Left Side: Icon Grid -->
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="2"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:scrollbarFadeDuration="0"
|
||||
android:verticalScrollbarPosition="left">
|
||||
<GridLayout
|
||||
android:id="@+id/edit_socket_type_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="3"
|
||||
android:paddingEnd="16dp"/>
|
||||
</ScrollView>
|
||||
|
||||
<!-- Right Side: Input Fields -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/edit_socket_power_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:endIconMode="clear_text">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/edit_socket_power"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:completionThreshold="1"
|
||||
android:gravity="end"
|
||||
android:hint="@string/charge_socket_power"
|
||||
android:inputType="numberDecimal"
|
||||
app:simpleItems="@array/charge_sockets_common_powers" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/edit_socket_count_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:endIconMode="clear_text">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/edit_socket_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:completionThreshold="1"
|
||||
android:gravity="end"
|
||||
android:hint="@string/charge_socket_count"
|
||||
android:inputType="numberDecimal"
|
||||
app:simpleItems="@array/charge_sockets_common_counts" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
30
android/app/src/main/res/layout/button_new_item.xml
Normal file
30
android/app/src/main/res/layout/button_new_item.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView 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:id="@+id/button_new_item"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_margin="4dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
tools:showIn="@layout/item_charging_sockets">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="+"
|
||||
android:textSize="40sp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
</RelativeLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
18
android/app/src/main/res/layout/button_socket_type.xml
Normal file
18
android/app/src/main/res/layout/button_socket_type.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:id="@+id/button_socket_type"
|
||||
android:checkable="true"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="2dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/charge_socket_unknown_other"
|
||||
app:icon="@drawable/ic_charge_socket_unknown"
|
||||
app:iconGravity="textTop"
|
||||
android:textAppearance="@style/MwmTextAppearance.Body5"
|
||||
/>
|
||||
66
android/app/src/main/res/layout/dialog_edit_socket.xml
Normal file
66
android/app/src/main/res/layout/dialog_edit_socket.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<!-- dynamically filled with one button for each socket type -->
|
||||
<GridLayout
|
||||
android:id="@+id/edit_socket_type_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="4"
|
||||
android:padding="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/edit_socket_power_layout"
|
||||
android:layout_width="0dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
app:endIconMode="clear_text">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/edit_socket_power"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:completionThreshold="1"
|
||||
android:inputType="numberDecimal"
|
||||
android:hint="@string/charge_socket_power"
|
||||
app:simpleItems="@array/charge_sockets_common_powers"
|
||||
/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/edit_socket_count_layout"
|
||||
android:layout_width="0dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
app:endIconMode="clear_text"
|
||||
>
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/edit_socket_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:completionThreshold="1"
|
||||
android:inputType="numberDecimal"
|
||||
android:hint="@string/charge_socket_count"
|
||||
app:simpleItems="@array/charge_sockets_common_counts"
|
||||
/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -62,6 +62,28 @@
|
||||
tools:text="Ololo"/>
|
||||
</RelativeLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/cv__charging_station"
|
||||
style="@style/MwmWidget.Editor.CardView" >
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="@dimen/margin_base"
|
||||
android:paddingStart="@dimen/margin_base">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_base"
|
||||
android:fontFamily="@string/robotoMedium"
|
||||
android:text="@string/charging_station_available_sockets"
|
||||
android:textAppearance="@style/MwmTextAppearance.Body3"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
<include
|
||||
android:id="@+id/block_charge_sockets"
|
||||
layout="@layout/item_charging_sockets"/>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/cv__name"
|
||||
style="@style/MwmWidget.Editor.CardView">
|
||||
@@ -125,6 +147,7 @@
|
||||
layout="@layout/item_editor_input"/>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/cv__details"
|
||||
style="@style/MwmWidget.Editor.CardView">
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_margin="4dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
|
||||
25
android/app/src/main/res/layout/item_charging_sockets.xml
Normal file
25
android/app/src/main/res/layout/item_charging_sockets.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<GridLayout
|
||||
android:id="@+id/socket_grid_editor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="3"
|
||||
android:padding="0dp"
|
||||
android:alignmentMode="alignMargins"
|
||||
android:columnOrderPreserved="false">
|
||||
|
||||
<include layout="@layout/button_new_item" />
|
||||
</GridLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit_socket_info_tooltip"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
</LinearLayout>
|
||||
@@ -877,11 +877,14 @@
|
||||
<string name="hours_confirmed_time_ago">Confirmado %s</string>
|
||||
<string name="share_track">Compartir traza</string>
|
||||
<string name="pref_tts_no_system_tts_short">No se ha encontrado ningún motor de texto-a-voz, comprueba la configuración de la aplicación</string>
|
||||
<string name="unknown_power_output">potencia desconocida</string>
|
||||
<string name="unknown_power_output">desconocida</string>
|
||||
<string name="charge_socket_type2">Tipo 2 (sin cable)</string>
|
||||
<string name="charge_socket_type2_cable">Tipo 2 (con cable)</string>
|
||||
<string name="charge_socket_type2_combo">Tipo 2 combo</string>
|
||||
<string name="charge_socket_type1">Tipo 1</string>
|
||||
<string name="charge_socket_nacs">NACS</string>
|
||||
<string name="charge_socket_chademo">CHAdeMO</string>
|
||||
<string name="charge_socket_count">Recuento</string>
|
||||
<string name="charge_socket_power">Potencia (kW)</string>
|
||||
<string name="unknown_count">desconocido</string>
|
||||
</resources>
|
||||
|
||||
@@ -876,11 +876,14 @@
|
||||
<string name="existence_confirmed_time_ago">Existence confirmée %s</string>
|
||||
<string name="hours_confirmed_time_ago">Confirmé %s</string>
|
||||
<string name="pref_tts_no_system_tts_short">Impossible de lire ce texte à voix haute, vérifiez les paramètres de l’application</string>
|
||||
<string name="unknown_power_output">puissance inconnue</string>
|
||||
<string name="unknown_power_output">inconnue</string>
|
||||
<string name="charge_socket_type2">Type 2 (sans câble)</string>
|
||||
<string name="charge_socket_type2_cable">Type 2 (avec câble)</string>
|
||||
<string name="charge_socket_type2_combo">Type 2 combo</string>
|
||||
<string name="charge_socket_type1">Type 1</string>
|
||||
<string name="charge_socket_nacs">NACS</string>
|
||||
<string name="charge_socket_chademo">CHAdeMO</string>
|
||||
<string name="charge_socket_count">Quantité</string>
|
||||
<string name="charge_socket_power">Puissance (kW)</string>
|
||||
<string name="unknown_count">inconnu</string>
|
||||
</resources>
|
||||
|
||||
@@ -22,4 +22,32 @@
|
||||
<item>@drawable/route_point_19</item>
|
||||
<item>@drawable/route_point_20</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="charge_socket_types">
|
||||
<item>type2_combo</item>
|
||||
<item>nacs</item>
|
||||
<item>chademo</item>
|
||||
<item>type1</item>
|
||||
<item>type2_cable</item>
|
||||
<item>type2</item>
|
||||
<item>unknown</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="charge_sockets_common_powers">
|
||||
<item>22</item>
|
||||
<item>43</item>
|
||||
<item>50</item>
|
||||
<item>100</item>
|
||||
<item>150</item>
|
||||
<item>200</item>
|
||||
<item>350</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="charge_sockets_common_counts">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>4</item>
|
||||
<item>8</item>
|
||||
<item>10</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
@@ -913,7 +913,7 @@
|
||||
<string name="share_track">Share Track</string>
|
||||
<string name="delete_track_dialog_title">Delete %s?</string>
|
||||
<string name="pref_tts_no_system_tts_short">No text-to-speech engine found, check the app settings</string>
|
||||
<string name="unknown_power_output">unknown power</string>
|
||||
<string name="unknown_power_output">unknown</string>
|
||||
<string name="charge_socket_type2">Type 2 (no cable)</string>
|
||||
<string name="charge_socket_type2_cable">Type 2 (w/ cable)</string>
|
||||
<string name="charge_socket_type2_combo">Type 2 combo</string>
|
||||
@@ -921,4 +921,14 @@
|
||||
<string name="charge_socket_nacs">NACS</string>
|
||||
<string name="charge_socket_chademo">CHAdeMO</string>
|
||||
<string name="unknown_socket_type">unknown socket</string>
|
||||
<string name="unknow_socket_type">unknown socket</string>
|
||||
<string name="edit_socket_info_tooltip">Create new sockets or edit existing ones.</string>
|
||||
<string name="charging_station_available_sockets">Available sockets</string>
|
||||
<string name="charge_socket_unknown_other">Other or unknown</string>
|
||||
<string name="charge_socket_count">Count</string>
|
||||
<string name="charge_socket_power">Power (kW)</string>
|
||||
<string name="editor_socket">Edit socket</string>
|
||||
<string name="unknown_count">unknown</string>
|
||||
<string name="error_value_must_be_positive">The value must be positive</string>
|
||||
<string name="error_invalid_number">Invalid number</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "indexer/cuisines.hpp"
|
||||
#include "indexer/editable_map_object.hpp"
|
||||
#include "indexer/feature_charge_sockets.hpp"
|
||||
#include "indexer/feature_utils.hpp"
|
||||
#include "indexer/validate_and_format_contacts.hpp"
|
||||
|
||||
@@ -94,6 +95,64 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeSetOpeningHo
|
||||
g_editableMapObject.SetOpeningHours(jni::ToNativeString(env, value));
|
||||
}
|
||||
|
||||
JNIEXPORT jobjectArray JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeGetChargeSockets(JNIEnv * env, jclass)
|
||||
{
|
||||
auto sockets = g_editableMapObject.GetChargeSockets();
|
||||
|
||||
jclass descClass = env->FindClass("app/organicmaps/sdk/bookmarks/data/ChargeSocketDescriptor");
|
||||
jmethodID ctor = env->GetMethodID(descClass, "<init>", "(Ljava/lang/String;ID)V");
|
||||
|
||||
// Create a Java array
|
||||
jobjectArray result = env->NewObjectArray(sockets.size(), descClass, nullptr);
|
||||
|
||||
for (size_t i = 0; i < sockets.size(); ++i)
|
||||
{
|
||||
auto const & s = sockets[i];
|
||||
|
||||
jstring jType = env->NewStringUTF(s.type.c_str());
|
||||
jobject jDesc = env->NewObject(descClass, ctor, jType, (jint)s.count, (jdouble)s.power);
|
||||
|
||||
env->SetObjectArrayElement(result, i, jDesc);
|
||||
|
||||
env->DeleteLocalRef(jType);
|
||||
env->DeleteLocalRef(jDesc);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeSetChargeSockets(JNIEnv * env, jclass,
|
||||
jobjectArray jSockets)
|
||||
{
|
||||
ChargeSocketsHelper chargeSockets;
|
||||
|
||||
jsize len = env->GetArrayLength(jSockets);
|
||||
|
||||
jclass descClass = env->FindClass("app/organicmaps/sdk/bookmarks/data/ChargeSocketDescriptor");
|
||||
jfieldID fidType = env->GetFieldID(descClass, "type", "Ljava/lang/String;");
|
||||
jfieldID fidCount = env->GetFieldID(descClass, "count", "I");
|
||||
jfieldID fidPower = env->GetFieldID(descClass, "power", "D");
|
||||
|
||||
for (jsize i = 0; i < len; ++i)
|
||||
{
|
||||
jobject jDesc = env->GetObjectArrayElement(jSockets, i);
|
||||
|
||||
jstring jType = (jstring)env->GetObjectField(jDesc, fidType);
|
||||
char const * cType = env->GetStringUTFChars(jType, nullptr);
|
||||
|
||||
jint count = env->GetIntField(jDesc, fidCount);
|
||||
jdouble power = env->GetDoubleField(jDesc, fidPower);
|
||||
|
||||
chargeSockets.AddSocket(cType, static_cast<unsigned int>(count), static_cast<double>(power));
|
||||
|
||||
env->ReleaseStringUTFChars(jType, cType);
|
||||
env->DeleteLocalRef(jType);
|
||||
env->DeleteLocalRef(jDesc);
|
||||
}
|
||||
|
||||
g_editableMapObject.SetChargeSockets(chargeSockets.ToString());
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_app_organicmaps_sdk_editor_Editor_nativeGetMetadata(JNIEnv * env, jclass, jint id)
|
||||
{
|
||||
auto const metaID = static_cast<osm::MapObject::MetadataID>(id);
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Size;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import app.organicmaps.sdk.Framework;
|
||||
import app.organicmaps.sdk.bookmarks.data.ChargeSocketDescriptor;
|
||||
import app.organicmaps.sdk.bookmarks.data.Metadata;
|
||||
import app.organicmaps.sdk.editor.data.FeatureCategory;
|
||||
import app.organicmaps.sdk.editor.data.Language;
|
||||
@@ -62,6 +63,8 @@ public final class Editor
|
||||
public static native void nativeSetMetadata(int id, String value);
|
||||
public static native String nativeGetOpeningHours();
|
||||
public static native void nativeSetOpeningHours(String openingHours);
|
||||
public static native ChargeSocketDescriptor[] nativeGetChargeSockets();
|
||||
public static native void nativeSetChargeSockets(ChargeSocketDescriptor[] sockets);
|
||||
public static String nativeGetPhone()
|
||||
{
|
||||
return nativeGetMetadata(Metadata.MetadataType.FMD_PHONE_NUMBER.toInt());
|
||||
|
||||
@@ -160,7 +160,26 @@
|
||||
<field name="drive_through">
|
||||
<tag k="drive_through" />
|
||||
</field>
|
||||
<!-- charging station socket information -->
|
||||
<field name="socket_type1_count">
|
||||
<tag k="socket:type1" />
|
||||
</field>
|
||||
<field name="socket_type1_output">
|
||||
<tag k="socket:type1:output" />
|
||||
</field>
|
||||
<field name="socket_type2_count">
|
||||
<tag k="socket:type2" />
|
||||
</field>
|
||||
<field name="socket_type2_output">
|
||||
<tag k="socket:type2:output" />
|
||||
</field>
|
||||
<!-- Field groups. -->
|
||||
<field_group name="charge_sockets">
|
||||
<field_ref name="socket_type1_count" />
|
||||
<field_ref name="socket_type1_output" />
|
||||
<field_ref name="socket_type2_count" />
|
||||
<field_ref name="socket_type2_output" />
|
||||
</field_group>
|
||||
<field_group name="address">
|
||||
<field_ref name="street" />
|
||||
<field_ref name="housenumber" />
|
||||
@@ -463,7 +482,9 @@
|
||||
<include group="poi_internet" />
|
||||
</type>
|
||||
<type id="amenity-charging_station">
|
||||
<include group="poi" />
|
||||
<include field="name" />
|
||||
<include group="charge_sockets" />
|
||||
<include field="opening_hours" />
|
||||
<include field="operator" />
|
||||
</type>
|
||||
<type id="building-guardhouse">
|
||||
|
||||
@@ -46,7 +46,12 @@ static std::unordered_map<std::string, EType> const kNamesToFMD = {
|
||||
{"drive_through", EType::FMD_DRIVE_THROUGH},
|
||||
{"website_menu", EType::FMD_WEBSITE_MENU},
|
||||
{"self_service", EType::FMD_SELF_SERVICE},
|
||||
{"outdoor_seating", EType::FMD_OUTDOOR_SEATING}
|
||||
{"outdoor_seating", EType::FMD_OUTDOOR_SEATING},
|
||||
// TODO(skadge): this won't work, obv
|
||||
{"socket_type1_count", EType::FMD_CHARGE_SOCKETS},
|
||||
{"socket_type1_output", EType::FMD_CHARGE_SOCKETS},
|
||||
{"socket_type2_count", EType::FMD_CHARGE_SOCKETS},
|
||||
{"socket_type2_output", EType::FMD_CHARGE_SOCKETS},
|
||||
/// @todo Add description?
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/edit_journal.hpp"
|
||||
#include "indexer/feature_charge_sockets.hpp"
|
||||
#include "indexer/feature_meta.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/postcodes_matcher.hpp"
|
||||
#include "indexer/validate_and_format_contacts.hpp"
|
||||
@@ -158,6 +160,7 @@ void EditableMapObject::ForEachMetadataItem(function<void(string_view tag, strin
|
||||
case MetadataID::FMD_EXTERNAL_URI:
|
||||
case MetadataID::FMD_WHEELCHAIR: // Value is runtime only, data is taken from the classificator types, should not
|
||||
// be used to update the OSM database
|
||||
case MetadataID::FMD_CHARGE_SOCKETS: // multiple keys; handled via the edit journal
|
||||
break;
|
||||
default: fn(ToString(type), value); break;
|
||||
}
|
||||
@@ -325,6 +328,14 @@ void EditableMapObject::SetOpeningHours(std::string oh)
|
||||
m_metadata.Set(MetadataID::FMD_OPEN_HOURS, std::move(oh));
|
||||
}
|
||||
|
||||
void EditableMapObject::SetChargeSockets(std::string sockets)
|
||||
{
|
||||
// parse the list of sockets provided by the frontend, and re-generate the
|
||||
// socket list, thus ensuring it is valid & sorted.
|
||||
ChargeSocketsHelper helper(sockets);
|
||||
m_metadata.Set(MetadataID::FMD_CHARGE_SOCKETS, helper.ToString());
|
||||
}
|
||||
|
||||
void EditableMapObject::SetInternet(feature::Internet internet)
|
||||
{
|
||||
m_metadata.Set(MetadataID::FMD_INTERNET, DebugPrint(internet));
|
||||
@@ -761,6 +772,10 @@ void EditableMapObject::LogDiffInJournal(EditableMapObject const & unedited_emo)
|
||||
for (uint8_t i = 0; i < static_cast<uint8_t>(feature::Metadata::FMD_COUNT); ++i)
|
||||
{
|
||||
auto const type = static_cast<feature::Metadata::EType>(i);
|
||||
|
||||
// CHARGE_SOCKETS have multiple keys/values; handled separately further down
|
||||
if (type == feature::Metadata::FMD_CHARGE_SOCKETS)
|
||||
continue;
|
||||
std::string_view const & value = GetMetadata(type);
|
||||
std::string_view const & old_value = unedited_emo.GetMetadata(type);
|
||||
|
||||
@@ -811,6 +826,15 @@ void EditableMapObject::LogDiffInJournal(EditableMapObject const & unedited_emo)
|
||||
|
||||
if (cuisinesModified)
|
||||
m_journal.AddTagChange("cuisine", strings::JoinStrings(old_cuisines, ";"), strings::JoinStrings(new_cuisines, ";"));
|
||||
|
||||
// charge sockets
|
||||
auto chargeSocketsDiff = ChargeSocketsHelper(GetChargeSockets()).Diff(unedited_emo.GetChargeSockets());
|
||||
for (auto const & kvdiff : chargeSocketsDiff)
|
||||
{
|
||||
std::string key, old_value, new_value;
|
||||
std::tie(key, old_value, new_value) = kvdiff;
|
||||
m_journal.AddTagChange(key, old_value, new_value);
|
||||
}
|
||||
}
|
||||
|
||||
bool AreObjectsEqualIgnoringStreet(EditableMapObject const & lhs, EditableMapObject const & rhs)
|
||||
|
||||
@@ -108,6 +108,7 @@ public:
|
||||
bool UpdateMetadataValue(std::string_view key, std::string value);
|
||||
|
||||
void SetOpeningHours(std::string oh);
|
||||
void SetChargeSockets(std::string sockets);
|
||||
void SetInternet(feature::Internet internet);
|
||||
|
||||
/// @param[in] cuisine is a vector of osm cuisine ids.
|
||||
|
||||
Reference in New Issue
Block a user