package org.wikipedia.wiktionary; import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import org.wikipedia.R; import org.wikipedia.WikipediaApp; import org.wikipedia.activity.FragmentUtil; import org.wikipedia.analytics.WiktionaryDialogFunnel; import org.wikipedia.dataclient.WikiSite; import org.wikipedia.dataclient.page.PageClient; import org.wikipedia.dataclient.page.PageClientFactory; import org.wikipedia.dataclient.restbase.RbDefinition; import org.wikipedia.dataclient.restbase.page.RbPageClient; import org.wikipedia.dataclient.restbase.page.RbPageClient.DefinitionCallback; import org.wikipedia.page.ExtendedBottomSheetDialogFragment; import org.wikipedia.page.LinkMovementMethodExt; import org.wikipedia.page.Namespace; import org.wikipedia.page.PageTitle; import org.wikipedia.util.StringUtil; import org.wikipedia.util.log.L; import org.wikipedia.views.AppTextView; import static org.wikipedia.util.L10nUtil.setConditionalLayoutDirection; import static org.wikipedia.util.StringUtil.addUnderscores; import static org.wikipedia.util.StringUtil.hasSectionAnchor; import static org.wikipedia.util.StringUtil.removeSectionAnchor; import static org.wikipedia.util.StringUtil.removeUnderscores; public class WiktionaryDialog extends ExtendedBottomSheetDialogFragment { public interface Callback { void wiktionaryShowDialogForTerm(@NonNull String term); } private static final String WIKTIONARY_DOMAIN = ".wiktionary.org"; private static final String TITLE = "title"; private static final String SELECTED_TEXT = "selected_text"; private static final String PATH_WIKI = "/wiki/"; private static final String PATH_CURRENT = "./"; private static String[] ENABLED_LANGUAGES = { "en" // English }; private ProgressBar progressBar; private PageTitle pageTitle; private String selectedText; private RbDefinition currentDefinition; private View rootView; private WiktionaryDialogFunnel funnel; public static WiktionaryDialog newInstance(@NonNull PageTitle title, @NonNull String selectedText) { WiktionaryDialog dialog = new WiktionaryDialog(); Bundle args = new Bundle(); args.putParcelable(TITLE, title); args.putString(SELECTED_TEXT, selectedText); dialog.setArguments(args); return dialog; } public static String[] getEnabledLanguages() { return ENABLED_LANGUAGES; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); pageTitle = getArguments().getParcelable(TITLE); selectedText = getArguments().getString(SELECTED_TEXT); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { rootView = inflater.inflate(R.layout.dialog_wiktionary, container); progressBar = (ProgressBar) rootView.findViewById(R.id.dialog_wiktionary_progress); TextView titleText = (TextView) rootView.findViewById(R.id.wiktionary_definition_dialog_title); titleText.setText(sanitizeForDialogTitle(selectedText)); setConditionalLayoutDirection(rootView, pageTitle.getWikiSite().languageCode()); loadDefinitions(); funnel = new WiktionaryDialogFunnel(WikipediaApp.getInstance(), selectedText); return rootView; } @Override public void onDismiss(DialogInterface dialogInterface) { super.onDismiss(dialogInterface); funnel.logClose(); } private void loadDefinitions() { if (selectedText.trim().isEmpty()) { displayNoDefinitionsFound(); return; } // TODO: centralize the Wiktionary domain better. Maybe a SharedPreference that defaults to // https://wiktionary.org. PageClient pageClient = PageClientFactory.create(new WikiSite(pageTitle.getWikiSite().languageCode() + WIKTIONARY_DOMAIN), Namespace.MAIN); if (pageClient instanceof RbPageClient) { ((RbPageClient) pageClient).define( addUnderscores(selectedText), definitionOnLoadCallback); } else { L.e("Wiktionary definitions require mobile content service loading!"); displayNoDefinitionsFound(); } } private DefinitionCallback definitionOnLoadCallback = new DefinitionCallback() { @Override public void success(@NonNull RbDefinition definition) { if (!isAdded()) { return; } if (!definition.hasError()) { progressBar.setVisibility(View.GONE); currentDefinition = definition; layOutDefinitionsByUsage(); } else { definition.logError("Wiktionary definition request failed"); } } @Override public void failure(@NonNull Throwable throwable) { if (!isAdded()) { return; } displayNoDefinitionsFound(); L.e("Wiktionary definition fetch error: " + throwable); } }; private void displayNoDefinitionsFound() { TextView noDefinitionsFoundView = (TextView) rootView.findViewById(R.id.wiktionary_no_definitions_found); noDefinitionsFoundView.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); } private void layOutDefinitionsByUsage() { LayoutInflater inflater = LayoutInflater.from(getContext()); LinearLayout fullDefinitionsList = (LinearLayout) rootView.findViewById(R.id.wiktionary_definitions_by_part_of_speech); RbDefinition.Usage[] usageList = currentDefinition.getUsagesForLang("en"); if (usageList == null || usageList.length == 0) { displayNoDefinitionsFound(); return; } for (RbDefinition.Usage usage : usageList) { View usageView = inflater.inflate(R.layout.item_wiktionary_definitions_list, (ViewGroup) rootView, false); layOutUsage(usage, usageView, inflater); fullDefinitionsList.addView(usageView); } } private void layOutUsage(RbDefinition.Usage currentUsage, View usageView, LayoutInflater inflater) { TextView partOfSpeechView = (TextView) usageView.findViewById(R.id.wiktionary_part_of_speech); partOfSpeechView.setText(currentUsage.getPartOfSpeech()); LinearLayout definitionsForPartOfSpeechList = (LinearLayout) usageView.findViewById(R.id.list_wiktionary_definitions_with_examples); for (int i = 0; i < currentUsage.getDefinitions().length; i++) { View definitionContainerView = inflater.inflate(R.layout.item_wiktionary_definition_with_examples, (ViewGroup) rootView, false); layOutDefinitionWithExamples(currentUsage.getDefinitions()[i], definitionContainerView, inflater, i + 1); definitionsForPartOfSpeechList.addView(definitionContainerView); } } private void layOutDefinitionWithExamples(RbDefinition.Definition currentDefinition, View definitionContainerView, LayoutInflater inflater, int count) { AppTextView definitionView = (AppTextView) definitionContainerView.findViewById(R.id.wiktionary_definition); String definitionWithCount = getCounterString(count) + currentDefinition.getDefinition(); definitionView.setText(StringUtil.fromHtml(definitionWithCount)); definitionView.setMovementMethod(linkMovementMethod); LinearLayout examplesView = (LinearLayout) definitionContainerView.findViewById(R.id.wiktionary_examples); if (currentDefinition.getExamples() != null) { layoutExamples(currentDefinition.getExamples(), examplesView, inflater); } } private String getCounterString(int count) { return count + ". "; } private void layoutExamples(String[] examples, LinearLayout examplesView, LayoutInflater inflater) { for (String example : examples) { AppTextView exampleView = (AppTextView) inflater.inflate(R.layout.item_wiktionary_example, (ViewGroup) rootView, false); exampleView.setText(StringUtil.fromHtml(example)); exampleView.setMovementMethod(linkMovementMethod); examplesView.addView(exampleView); } } private LinkMovementMethodExt linkMovementMethod = new LinkMovementMethodExt(new LinkMovementMethodExt.UrlHandler() { @Override public void onUrlClick(@NonNull String url, @Nullable String notUsed) { if (url.startsWith(PATH_WIKI) || url.startsWith(PATH_CURRENT)) { dismiss(); showNewDialogForLink(url); } } }); private String getTermFromWikiLink(String url) { return removeLinkFragment(url.substring(url.lastIndexOf("/") + 1)); } private String removeLinkFragment(String url) { return url.split("#")[0]; } private void showNewDialogForLink(String url) { Callback callback = callback(); if (callback != null) { callback.wiktionaryShowDialogForTerm(getTermFromWikiLink(url)); } } private String sanitizeForDialogTitle(String text) { if (hasSectionAnchor(text)) { text = removeSectionAnchor(text); } return removeUnderscores(text); } @Nullable private Callback callback() { return FragmentUtil.getCallback(this, Callback.class); } }