diff --git a/android/3rd_party/BottomSheet/build.gradle b/android/3rd_party/BottomSheet/build.gradle index f93a7a5d96..70f45ba9ad 100644 --- a/android/3rd_party/BottomSheet/build.gradle +++ b/android/3rd_party/BottomSheet/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.1.2' } } diff --git a/android/UnitTests/build.gradle b/android/UnitTests/build.gradle index e30a3877d5..cbd1b88ad0 100644 --- a/android/UnitTests/build.gradle +++ b/android/UnitTests/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/android/build.gradle b/android/build.gradle index e5ca7b798e..6e87bac5fe 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.1.2' classpath 'io.fabric.tools:gradle:1.+' } } diff --git a/android/jni/com/mapswithme/core/jni_helper.hpp b/android/jni/com/mapswithme/core/jni_helper.hpp index 8384784437..6ac621c37c 100644 --- a/android/jni/com/mapswithme/core/jni_helper.hpp +++ b/android/jni/com/mapswithme/core/jni_helper.hpp @@ -51,20 +51,28 @@ jobject GetNewParcelablePointD(JNIEnv * env, m2::PointD const & point); jobject GetNewPoint(JNIEnv * env, m2::PointD const & point); jobject GetNewPoint(JNIEnv * env, m2::PointI const & point); -template -jobjectArray ToJavaArray(JNIEnv * env, jclass clazz, vector const & src, TToJavaFn && toJavaFn) +template +jobjectArray ToJavaArray(JNIEnv * env, jclass clazz, TIt begin, TIt end, size_t const size, TToJavaFn && toJavaFn) { - size_t const size = src.size(); jobjectArray jArray = env->NewObjectArray((jint) size, clazz, 0); - for (size_t i = 0; i < size; i++) + size_t i = 0; + for (auto it = begin; it != end; ++it) { - TScopedLocalRef jItem(env, toJavaFn(env, src[i])); + TScopedLocalRef jItem(env, toJavaFn(env, *it)); env->SetObjectArrayElement(jArray, i, jItem.get()); + ++i; } return jArray; } + +template +jobjectArray ToJavaArray(JNIEnv * env, jclass clazz, TContainer const & src, TToJavaFn && toJavaFn) +{ + return ToJavaArray(env, clazz, begin(src), end(src), src.size(), forward(toJavaFn)); +} + jobjectArray ToJavaStringArray(JNIEnv * env, vector const & src); void DumpDalvikReferenceTables(); -} +} // namespace jni diff --git a/android/jni/com/mapswithme/maps/editor/Editor.cpp b/android/jni/com/mapswithme/maps/editor/Editor.cpp index b572988e2b..8b7c349cce 100644 --- a/android/jni/com/mapswithme/maps/editor/Editor.cpp +++ b/android/jni/com/mapswithme/maps/editor/Editor.cpp @@ -3,6 +3,8 @@ #include "com/mapswithme/core/jni_helper.hpp" #include "com/mapswithme/maps/Framework.hpp" +#include "coding/multilang_utf8_string.hpp" + #include "base/assert.hpp" #include "base/logging.hpp" #include "base/string_utils.hpp" @@ -344,13 +346,18 @@ Java_com_mapswithme_maps_editor_Editor_nativeGetNearbyStreets(JNIEnv * env, jcla JNIEXPORT jobjectArray JNICALL Java_com_mapswithme_maps_editor_Editor_nativeGetSupportedLanguages(JNIEnv * env, jclass clazz) { - // TODO (yunikkk) implement + using TLang = StringUtf8Multilang::Lang; //public Language(@NonNull String code, @NonNull String name) -// static jclass const langClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/editor/data/Language"); -// static jmethodID const langCtor = jni::GetConstructorID(env, langClass, "(Ljava/lang/String;Ljava/lang/String;)V"); + static jclass const langClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/editor/data/Language"); + static jmethodID const langCtor = jni::GetConstructorID(env, langClass, "(Ljava/lang/String;Ljava/lang/String;)V"); -// return jni::ToJavaArray(env, langClass, g_editableMapObject.GetBuildingLevels(), ); - return nullptr; + return jni::ToJavaArray(env, langClass, StringUtf8Multilang::GetSupportedLanguages(), + [](JNIEnv * env, TLang const & lang) + { + jni::TScopedLocalRef const code(env, jni::ToJavaString(env, lang.m_code)); + jni::TScopedLocalRef const name(env, jni::ToJavaString(env, lang.m_name)); + return env->NewObject(langClass, langCtor, code.get(), name.get()); + }); } JNIEXPORT jstring JNICALL @@ -583,4 +590,12 @@ Java_com_mapswithme_maps_editor_Editor_nativeIsMapObjectUploaded(JNIEnv * env, j { return osm::Editor::Instance().IsFeatureUploaded(g_editableMapObject.GetID().m_mwmId, g_editableMapObject.GetID().m_index); } + +// static nativeMakeLocalizedName(String langCode, String name); +JNIEXPORT jobject JNICALL +Java_com_mapswithme_maps_editor_Editor_nativeMakeLocalizedName(JNIEnv * env, jclass clazz, jstring code, jstring name) +{ + osm::LocalizedName localizedName(jni::ToNativeString(env, code), jni::ToNativeString(env, name)); + return ToJavaName(env, localizedName); +} } // extern "C" diff --git a/android/res/drawable-ldpi/ic_leadreboard.png b/android/res/drawable-ldpi/ic_leadreboard.png deleted file mode 100644 index 477f80abef..0000000000 Binary files a/android/res/drawable-ldpi/ic_leadreboard.png and /dev/null differ diff --git a/android/res/drawable-ldpi/ic_more.png b/android/res/drawable-ldpi/ic_more.png deleted file mode 100644 index fde57e5936..0000000000 Binary files a/android/res/drawable-ldpi/ic_more.png and /dev/null differ diff --git a/android/src/com/mapswithme/maps/editor/Editor.java b/android/src/com/mapswithme/maps/editor/Editor.java index 2812281bd8..3bd5321189 100644 --- a/android/src/com/mapswithme/maps/editor/Editor.java +++ b/android/src/com/mapswithme/maps/editor/Editor.java @@ -105,6 +105,7 @@ public final class Editor public static native void nativeSetDefaultName(String name); public static native @NonNull LocalizedName[] nativeGetLocalizedNames(); public static native void nativeSetLocalizedNames(@NonNull LocalizedName[] names); + public static native LocalizedName nativeMakeLocalizedName(String langCode, String name); public static native Language[] nativeGetSupportedLanguages(); public static native LocalizedStreet nativeGetStreet(); diff --git a/android/src/com/mapswithme/maps/editor/EditorFragment.java b/android/src/com/mapswithme/maps/editor/EditorFragment.java index fcd3d5a837..8335e8f5c5 100644 --- a/android/src/com/mapswithme/maps/editor/EditorFragment.java +++ b/android/src/com/mapswithme/maps/editor/EditorFragment.java @@ -17,6 +17,7 @@ import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; @@ -37,6 +38,8 @@ import org.solovyev.android.views.llm.LinearLayoutManager; public class EditorFragment extends BaseMwmFragment implements View.OnClickListener, EditTextDialogFragment.OnTextSaveListener { + final static String LAST_LOCALIZED_NAME_INDEX = "LastLocalizedNameIndex"; + private TextView mCategory; private View mCardName; private View mCardAddress; @@ -44,6 +47,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe private EditText mName; private RecyclerView mLocalizedNames; + private final RecyclerView.AdapterDataObserver mLocalizedNamesObserver = new RecyclerView.AdapterDataObserver() { @Override @@ -76,6 +80,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe refreshLocalizedNames(); } }; + private MultilanguageAdapter mLocalizedNamesAdapter; private TextView mLocalizedShow; private boolean mIsLocalizedShown; @@ -195,6 +200,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe refreshOpeningTime(); refreshEditableFields(); refreshResetButton(); + refreshLocalizedNames(); } @Override @@ -218,7 +224,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe Editor.nativeSetEmail(mEmail.getText().toString()); Editor.nativeSetHasWifi(mWifi.isChecked()); Editor.nativeSetOperator(mOperator.getText().toString()); - // TODO set localizated names + Editor.nativeSetLocalizedNames(mParent.getLocalizedNamesAsArray()); return true; } @@ -324,6 +330,47 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe } } + private void initLocalizedNameView(final View view) + { + mLocalizedNames = (RecyclerView) view.findViewById(R.id.recycler); + mLocalizedNames.setNestedScrollingEnabled(false); + mLocalizedNames.setLayoutManager(new LinearLayoutManager(getActivity())); + mLocalizedNamesAdapter = new MultilanguageAdapter(mParent); + mLocalizedNames.setAdapter(mLocalizedNamesAdapter); + mLocalizedNamesAdapter.registerAdapterDataObserver(mLocalizedNamesObserver); + refreshLocalizedNames(); + + final Bundle args = getArguments(); + if (args == null || !args.containsKey(LAST_LOCALIZED_NAME_INDEX)) + { + showLocalizedNames(false); + return; + } + showLocalizedNames(true); + UiUtils.waitLayout(mLocalizedNames, new ViewTreeObserver.OnGlobalLayoutListener() + { + @Override + public void onGlobalLayout() + { + LinearLayoutManager lm = (LinearLayoutManager) mLocalizedNames.getLayoutManager(); + int position = args.getInt(LAST_LOCALIZED_NAME_INDEX); + + View nameItem = lm.findViewByPosition(position); + + int cvNameTop = view.findViewById(R.id.cv__name).getTop(); + int nameItemTop = nameItem.getTop(); + + view.scrollTo(0, cvNameTop + nameItemTop); + + // TODO(mgsergio): Uncomment if focus and keyboard are required. + // TODO(mgsergio): Keyboard doesn't want to hide. Only pressing back button works. + // View nameItemInput = nameItem.findViewById(R.id.input); + // nameItemInput.requestFocus(); + // InputUtils.showKeyboard(nameItemInput); + } + }); + } + private void initViews(View view) { final View categoryBlock = view.findViewById(R.id.category); @@ -335,18 +382,10 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe mCardAddress = view.findViewById(R.id.cv__address); mCardMetadata = view.findViewById(R.id.cv__metadata); mName = findInput(mCardName); - // TODO uncomment and finish localized name - // view.findViewById(R.id.add_langs).setOnClickListener(this); - UiUtils.hide(view.findViewById(R.id.add_langs)); + view.findViewById(R.id.add_langs).setOnClickListener(this); mLocalizedShow = (TextView) view.findViewById(R.id.show_langs); mLocalizedShow.setOnClickListener(this); - mLocalizedNames = (RecyclerView) view.findViewById(R.id.recycler); - mLocalizedNames.setLayoutManager(new LinearLayoutManager(getActivity())); - mLocalizedNamesAdapter = new MultilanguageAdapter(Editor.nativeGetLocalizedNames()); - mLocalizedNames.setAdapter(mLocalizedNamesAdapter); - mLocalizedNamesAdapter.registerAdapterDataObserver(mLocalizedNamesObserver); - refreshLocalizedNames(); - showLocalizedNames(false); + initLocalizedNameView(view); // Address view.findViewById(R.id.block_street).setOnClickListener(this); @@ -462,9 +501,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe private void refreshLocalizedNames() { - // TODO uncomment and finish localized names - // UiUtils.showIf(mLocalizedNamesAdapter.getItemCount() > 0, mLocalizedShow); - UiUtils.hide(mLocalizedNames, mLocalizedShow); + UiUtils.showIf(mLocalizedNamesAdapter.getItemCount() > 0, mLocalizedShow); } private void showLocalizedNames(boolean show) diff --git a/android/src/com/mapswithme/maps/editor/EditorHostFragment.java b/android/src/com/mapswithme/maps/editor/EditorHostFragment.java index f253b55c25..116d900d61 100644 --- a/android/src/com/mapswithme/maps/editor/EditorHostFragment.java +++ b/android/src/com/mapswithme/maps/editor/EditorHostFragment.java @@ -16,6 +16,8 @@ import com.mapswithme.maps.MwmActivity; import com.mapswithme.maps.R; import com.mapswithme.maps.base.BaseMwmToolbarFragment; import com.mapswithme.maps.base.OnBackPressListener; +import com.mapswithme.maps.editor.data.Language; +import com.mapswithme.maps.editor.data.LocalizedName; import com.mapswithme.maps.editor.data.LocalizedStreet; import com.mapswithme.maps.widget.SearchToolbarController; import com.mapswithme.maps.widget.ToolbarController; @@ -24,8 +26,11 @@ import com.mapswithme.util.UiUtils; import com.mapswithme.util.Utils; import com.mapswithme.util.statistics.Statistics; +import java.util.ArrayList; +import java.util.List; + public class EditorHostFragment extends BaseMwmToolbarFragment - implements OnBackPressListener, View.OnClickListener + implements OnBackPressListener, View.OnClickListener, LanguagesFragment.Listener { private boolean mIsNewObject; @@ -40,6 +45,48 @@ public class EditorHostFragment extends BaseMwmToolbarFragment private Mode mMode; + /** + * A list of localized names added by a user and those that were in metadata. + */ + private static final List sLocalizedNames = new ArrayList<>(); + + /** + * Used in MultilanguageAdapter to show, select and remove items. + */ + List getLocalizedNames() + { + return sLocalizedNames; + } + + public LocalizedName[] getLocalizedNamesAsArray() + { + return sLocalizedNames.toArray(new LocalizedName[sLocalizedNames.size()]); + } + + void setLocalizedNames(LocalizedName[] names) + { + sLocalizedNames.clear(); + for (LocalizedName name : names) + { + if (name.code == LocalizedName.DEFAULT_LANG_CODE) + continue; + sLocalizedNames.add(name); + } + } + + /** + * Sets .name of an index item to name. + */ + void setName(String name, int index) + { + sLocalizedNames.get(index).name = name; + } + + void addLocalizedName(LocalizedName name) + { + sLocalizedNames.add(name); + } + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) @@ -65,6 +112,7 @@ public class EditorHostFragment extends BaseMwmToolbarFragment mIsNewObject = getArguments().getBoolean(EditorActivity.EXTRA_NEW_OBJECT, false); mToolbarController.setTitle(getTitle()); + setLocalizedNames(Editor.nativeGetLocalizedNames()); editMapObject(); } @@ -104,11 +152,19 @@ public class EditorHostFragment extends BaseMwmToolbarFragment } protected void editMapObject() + { + editMapObject(false /* focusToLastLocalizedName */); + } + + protected void editMapObject(boolean focusToLastLocalizedName) { mMode = Mode.MAP_OBJECT; ((SearchToolbarController) mToolbarController).showControls(false); mToolbarController.setTitle(getTitle()); - final Fragment editorFragment = Fragment.instantiate(getActivity(), EditorFragment.class.getName()); + Bundle args = new Bundle(); + if (focusToLastLocalizedName) + args.putInt(EditorFragment.LAST_LOCALIZED_NAME_INDEX, sLocalizedNames.size() - 1); + final Fragment editorFragment = Fragment.instantiate(getActivity(), EditorFragment.class.getName(), args); getChildFragmentManager().beginTransaction() .replace(R.id.fragment_container, editorFragment, EditorFragment.class.getName()) .commit(); @@ -133,7 +189,12 @@ public class EditorHostFragment extends BaseMwmToolbarFragment protected void addLocalizedLanguage() { - editWithFragment(Mode.LANGUAGE, R.string.choose_language, null, LanguagesFragment.class, false); + Bundle args = new Bundle(); + ArrayList languages = new ArrayList<>(sLocalizedNames.size()); + for (LocalizedName name : sLocalizedNames) + languages.add(name.lang); + args.putStringArrayList(LanguagesFragment.EXISTING_LOCALIZED_NAMES, languages); + editWithFragment(Mode.LANGUAGE, R.string.choose_language, args, LanguagesFragment.class, false); } private void editWithFragment(Mode newMode, @StringRes int toolbarTitle, @Nullable Bundle args, Class fragmentClass, boolean showSearch) @@ -249,4 +310,11 @@ public class EditorHostFragment extends BaseMwmToolbarFragment { return mIsNewObject; } + + @Override + public void onLanguageSelected(Language lang) + { + addLocalizedName(Editor.nativeMakeLocalizedName(lang.code, "")); + editMapObject(true /* focusToLastLocalizedName */); + } } diff --git a/android/src/com/mapswithme/maps/editor/LanguagesFragment.java b/android/src/com/mapswithme/maps/editor/LanguagesFragment.java index 6a981f908b..d1e8952a23 100644 --- a/android/src/com/mapswithme/maps/editor/LanguagesFragment.java +++ b/android/src/com/mapswithme/maps/editor/LanguagesFragment.java @@ -1,12 +1,24 @@ package com.mapswithme.maps.editor; +import android.os.Bundle; import android.support.v7.widget.RecyclerView; import com.mapswithme.maps.base.BaseMwmRecyclerFragment; import com.mapswithme.maps.editor.data.Language; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + public class LanguagesFragment extends BaseMwmRecyclerFragment { + final static String EXISTING_LOCALIZED_NAMES = "ExistingLocalizedNames"; + public interface Listener { void onLanguageSelected(Language language); @@ -15,14 +27,30 @@ public class LanguagesFragment extends BaseMwmRecyclerFragment @Override protected RecyclerView.Adapter createAdapter() { - return new LanguagesAdapter(this, Editor.nativeGetSupportedLanguages()); + Bundle args = getArguments(); + Set existingLanguages = new HashSet<>(args.getStringArrayList(EXISTING_LOCALIZED_NAMES)); + + List languages = new ArrayList<>(); + for (Language lang : Editor.nativeGetSupportedLanguages()) + { + if (!existingLanguages.contains(lang.code)) + languages.add(lang); + } + + Collections.sort(languages, new Comparator() + { + @Override + public int compare(Language lhs, Language rhs) { + return lhs.name.compareTo(rhs.name); + } + }); + + return new LanguagesAdapter(this, languages.toArray(new Language[languages.size()])); } protected void onLanguageSelected(Language language) { - if (getActivity() instanceof Listener) - ((Listener) getActivity()).onLanguageSelected(language); - else if (getParentFragment() instanceof Listener) + if (getParentFragment() instanceof Listener) ((Listener) getParentFragment()).onLanguageSelected(language); } } diff --git a/android/src/com/mapswithme/maps/editor/MultilanguageAdapter.java b/android/src/com/mapswithme/maps/editor/MultilanguageAdapter.java index 72f5c692d4..4fc1eca797 100644 --- a/android/src/com/mapswithme/maps/editor/MultilanguageAdapter.java +++ b/android/src/com/mapswithme/maps/editor/MultilanguageAdapter.java @@ -1,9 +1,6 @@ package com.mapswithme.maps.editor; -import android.support.v7.util.SortedList; import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.util.SortedListAdapterCallback; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,55 +8,29 @@ import android.widget.EditText; import com.mapswithme.maps.R; import com.mapswithme.maps.editor.data.LocalizedName; +import com.mapswithme.util.StringUtils; +import com.mapswithme.util.UiUtils; + +import java.util.List; public class MultilanguageAdapter extends RecyclerView.Adapter { - private SortedList mNames; + private final List mNames; + private EditorHostFragment mHostFragment; - MultilanguageAdapter(LocalizedName[] names) + // TODO(mgsergio): Refactor: don't pass the whole EditorHostFragment. + MultilanguageAdapter(EditorHostFragment hostFragment) { - mNames = new SortedList<>(LocalizedName.class, - new SortedListAdapterCallback(this) - { - @Override - public int compare(LocalizedName o1, LocalizedName o2) - { - return o1.lang.compareTo(o2.lang); - } - - @Override - public boolean areContentsTheSame(LocalizedName oldItem, LocalizedName newItem) - { - return TextUtils.equals(oldItem.name, newItem.name); - } - - @Override - public boolean areItemsTheSame(LocalizedName item1, LocalizedName item2) - { - return item1.code == item2.code; - } - }, - names.length); - - // skip default name - for (int i = 1; i < names.length; i++) - mNames.add(names[i]); - } - - public void setNames(SortedList names) - { - mNames = names; - } - - public SortedList getNames() - { - return mNames; + mHostFragment = hostFragment; + mNames = hostFragment.getLocalizedNames(); } @Override public Holder onCreateViewHolder(ViewGroup parent, int viewType) { final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_localized_name, parent, false); + // TODO(mgsergio): Deletion is not implemented. + UiUtils.hide(view.findViewById(R.id.delete)); return new Holder(view); } @@ -85,12 +56,25 @@ public class MultilanguageAdapter extends RecyclerView.Adapter EditableMapObject::GetLocalizedNames() const vector result; m_name.ForEach([&result](int8_t code, string const & name) -> bool { - result.push_back({code, StringUtf8Multilang::GetLangByCode(code), - StringUtf8Multilang::GetLangNameByCode(code), name}); + result.push_back({code, name}); return true; }); return result; @@ -212,7 +231,7 @@ bool EditableMapObject::ValidateBuildingLevels(string const & buildingLevels) // static bool EditableMapObject::ValidateHouseNumber(string const & houseNumber) { - // TODO(mgsergio): Make a better validation, use real samples for example. + // TODO(mgsergio): Use LooksLikeHouseNumber! if (houseNumber.empty()) return true; diff --git a/indexer/editable_map_object.hpp b/indexer/editable_map_object.hpp index 64ee930028..365b23d20b 100644 --- a/indexer/editable_map_object.hpp +++ b/indexer/editable_map_object.hpp @@ -35,6 +35,9 @@ struct EditableProperties struct LocalizedName { + LocalizedName(int8_t code, string const & name); + LocalizedName(string const & langCode, string const & name); + // m_code, m_lang and m_langName are defined in StringUtf8Multilang. int8_t const m_code; // Non-owning pointers to internal static char const * array. diff --git a/iphone/Maps/Classes/Editor/MWMEditorViewController.mm b/iphone/Maps/Classes/Editor/MWMEditorViewController.mm index 7ed80e8b24..4d74b97ac5 100644 --- a/iphone/Maps/Classes/Editor/MWMEditorViewController.mm +++ b/iphone/Maps/Classes/Editor/MWMEditorViewController.mm @@ -90,8 +90,7 @@ vector getAdditionalLocalizedNames(osm::EditableMapObject co emo.GetName().ForEach([&result](int8_t code, string const & name) -> bool { if (code != StringUtf8Multilang::kDefaultCode) - result.push_back({code, StringUtf8Multilang::GetLangByCode(code), - StringUtf8Multilang::GetLangNameByCode(code), name}); + result.push_back({code, name}); return true; }); return result;