From da4c7e7ed675c3bf405668739c3012d140856109 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:42 +0200 Subject: Adding upstream version 126.0. Signed-off-by: Daniel Baumann --- .../components/feature/contextmenu/README.md | 92 + .../components/feature/contextmenu/build.gradle | 56 + .../feature/contextmenu/proguard-rules.pro | 21 + .../contextmenu/src/main/AndroidManifest.xml | 4 + .../feature/contextmenu/ContextMenuCandidate.kt | 689 +++++++ .../feature/contextmenu/ContextMenuFeature.kt | 140 ++ .../feature/contextmenu/ContextMenuFragment.kt | 173 ++ .../feature/contextmenu/ContextMenuUseCases.kt | 85 + .../contextmenu/DefaultSelectionActionDelegate.kt | 119 ++ .../ext/DefaultSelectionActionDelegate.kt | 32 + .../feature/contextmenu/facts/ContextMenuFacts.kt | 58 + .../layout/mozac_feature_contextmenu_dialog.xml | 39 + .../res/layout/mozac_feature_contextmenu_item.xml | 18 + .../res/layout/mozac_feature_contextmenu_title.xml | 23 + .../contextmenu/src/main/res/values-am/strings.xml | 53 + .../contextmenu/src/main/res/values-an/strings.xml | 51 + .../contextmenu/src/main/res/values-ar/strings.xml | 53 + .../src/main/res/values-ast/strings.xml | 53 + .../contextmenu/src/main/res/values-az/strings.xml | 51 + .../src/main/res/values-azb/strings.xml | 53 + .../src/main/res/values-ban/strings.xml | 21 + .../contextmenu/src/main/res/values-be/strings.xml | 53 + .../contextmenu/src/main/res/values-bg/strings.xml | 53 + .../contextmenu/src/main/res/values-bn/strings.xml | 51 + .../contextmenu/src/main/res/values-br/strings.xml | 53 + .../contextmenu/src/main/res/values-bs/strings.xml | 53 + .../contextmenu/src/main/res/values-ca/strings.xml | 53 + .../src/main/res/values-cak/strings.xml | 53 + .../src/main/res/values-ceb/strings.xml | 51 + .../src/main/res/values-ckb/strings.xml | 51 + .../contextmenu/src/main/res/values-co/strings.xml | 53 + .../contextmenu/src/main/res/values-cs/strings.xml | 53 + .../contextmenu/src/main/res/values-cy/strings.xml | 53 + .../contextmenu/src/main/res/values-da/strings.xml | 53 + .../contextmenu/src/main/res/values-de/strings.xml | 62 + .../src/main/res/values-dsb/strings.xml | 53 + .../contextmenu/src/main/res/values-el/strings.xml | 53 + .../src/main/res/values-en-rCA/strings.xml | 53 + .../src/main/res/values-en-rGB/strings.xml | 53 + .../contextmenu/src/main/res/values-eo/strings.xml | 53 + .../src/main/res/values-es-rAR/strings.xml | 53 + .../src/main/res/values-es-rCL/strings.xml | 53 + .../src/main/res/values-es-rES/strings.xml | 53 + .../src/main/res/values-es-rMX/strings.xml | 53 + .../contextmenu/src/main/res/values-es/strings.xml | 53 + .../contextmenu/src/main/res/values-et/strings.xml | 53 + .../contextmenu/src/main/res/values-eu/strings.xml | 53 + .../contextmenu/src/main/res/values-fa/strings.xml | 53 + .../contextmenu/src/main/res/values-ff/strings.xml | 41 + .../contextmenu/src/main/res/values-fi/strings.xml | 53 + .../contextmenu/src/main/res/values-fr/strings.xml | 59 + .../src/main/res/values-fur/strings.xml | 53 + .../src/main/res/values-fy-rNL/strings.xml | 53 + .../src/main/res/values-ga-rIE/strings.xml | 26 + .../contextmenu/src/main/res/values-gd/strings.xml | 53 + .../contextmenu/src/main/res/values-gl/strings.xml | 53 + .../contextmenu/src/main/res/values-gn/strings.xml | 53 + .../src/main/res/values-gu-rIN/strings.xml | 45 + .../src/main/res/values-hi-rIN/strings.xml | 51 + .../src/main/res/values-hil/strings.xml | 9 + .../contextmenu/src/main/res/values-hr/strings.xml | 53 + .../src/main/res/values-hsb/strings.xml | 53 + .../contextmenu/src/main/res/values-hu/strings.xml | 53 + .../src/main/res/values-hy-rAM/strings.xml | 53 + .../contextmenu/src/main/res/values-ia/strings.xml | 53 + .../contextmenu/src/main/res/values-in/strings.xml | 53 + .../contextmenu/src/main/res/values-is/strings.xml | 53 + .../contextmenu/src/main/res/values-it/strings.xml | 53 + .../contextmenu/src/main/res/values-iw/strings.xml | 53 + .../contextmenu/src/main/res/values-ja/strings.xml | 62 + .../contextmenu/src/main/res/values-ka/strings.xml | 53 + .../src/main/res/values-kaa/strings.xml | 53 + .../src/main/res/values-kab/strings.xml | 53 + .../contextmenu/src/main/res/values-kk/strings.xml | 53 + .../src/main/res/values-kmr/strings.xml | 53 + .../contextmenu/src/main/res/values-kn/strings.xml | 43 + .../contextmenu/src/main/res/values-ko/strings.xml | 53 + .../src/main/res/values-lij/strings.xml | 35 + .../contextmenu/src/main/res/values-lo/strings.xml | 53 + .../contextmenu/src/main/res/values-lt/strings.xml | 51 + .../src/main/res/values-mix/strings.xml | 17 + .../contextmenu/src/main/res/values-ml/strings.xml | 35 + .../contextmenu/src/main/res/values-mr/strings.xml | 49 + .../contextmenu/src/main/res/values-my/strings.xml | 51 + .../src/main/res/values-nb-rNO/strings.xml | 53 + .../src/main/res/values-ne-rNP/strings.xml | 51 + .../contextmenu/src/main/res/values-nl/strings.xml | 53 + .../src/main/res/values-nn-rNO/strings.xml | 53 + .../contextmenu/src/main/res/values-oc/strings.xml | 53 + .../contextmenu/src/main/res/values-or/strings.xml | 45 + .../src/main/res/values-pa-rIN/strings.xml | 53 + .../src/main/res/values-pa-rPK/strings.xml | 53 + .../contextmenu/src/main/res/values-pl/strings.xml | 53 + .../src/main/res/values-pt-rBR/strings.xml | 53 + .../src/main/res/values-pt-rPT/strings.xml | 53 + .../contextmenu/src/main/res/values-rm/strings.xml | 53 + .../contextmenu/src/main/res/values-ro/strings.xml | 51 + .../contextmenu/src/main/res/values-ru/strings.xml | 53 + .../src/main/res/values-sat/strings.xml | 53 + .../contextmenu/src/main/res/values-sc/strings.xml | 53 + .../contextmenu/src/main/res/values-si/strings.xml | 53 + .../contextmenu/src/main/res/values-sk/strings.xml | 53 + .../src/main/res/values-skr/strings.xml | 53 + .../contextmenu/src/main/res/values-sl/strings.xml | 53 + .../contextmenu/src/main/res/values-sq/strings.xml | 53 + .../contextmenu/src/main/res/values-sr/strings.xml | 53 + .../contextmenu/src/main/res/values-su/strings.xml | 53 + .../src/main/res/values-sv-rSE/strings.xml | 53 + .../contextmenu/src/main/res/values-ta/strings.xml | 51 + .../contextmenu/src/main/res/values-te/strings.xml | 51 + .../contextmenu/src/main/res/values-tg/strings.xml | 53 + .../contextmenu/src/main/res/values-th/strings.xml | 53 + .../contextmenu/src/main/res/values-tl/strings.xml | 51 + .../contextmenu/src/main/res/values-tr/strings.xml | 53 + .../src/main/res/values-trs/strings.xml | 53 + .../contextmenu/src/main/res/values-tt/strings.xml | 53 + .../src/main/res/values-tzm/strings.xml | 41 + .../contextmenu/src/main/res/values-ug/strings.xml | 53 + .../contextmenu/src/main/res/values-uk/strings.xml | 53 + .../contextmenu/src/main/res/values-ur/strings.xml | 51 + .../contextmenu/src/main/res/values-uz/strings.xml | 51 + .../src/main/res/values-vec/strings.xml | 49 + .../contextmenu/src/main/res/values-vi/strings.xml | 53 + .../contextmenu/src/main/res/values-yo/strings.xml | 51 + .../src/main/res/values-zh-rCN/strings.xml | 62 + .../src/main/res/values-zh-rTW/strings.xml | 62 + .../contextmenu/src/main/res/values/colors.xml | 8 + .../contextmenu/src/main/res/values/strings.xml | 56 + .../contextmenu/src/main/res/values/style.xml | 14 + .../contextmenu/ContextMenuCandidateTest.kt | 2063 ++++++++++++++++++++ .../feature/contextmenu/ContextMenuFeatureTest.kt | 403 ++++ .../feature/contextmenu/ContextMenuFragmentTest.kt | 347 ++++ .../DefaultSelectionActionDelegateTest.kt | 267 +++ .../org.mockito.plugins.MockMaker | 2 + .../src/test/resources/robolectric.properties | 1 + 135 files changed, 10423 insertions(+) create mode 100644 mobile/android/android-components/components/feature/contextmenu/README.md create mode 100644 mobile/android/android-components/components/feature/contextmenu/build.gradle create mode 100644 mobile/android/android-components/components/feature/contextmenu/proguard-rules.pro create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/AndroidManifest.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuFeature.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuFragment.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuUseCases.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/DefaultSelectionActionDelegate.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ext/DefaultSelectionActionDelegate.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/facts/ContextMenuFacts.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_dialog.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_item.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_title.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-am/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-an/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ar/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ast/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-az/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-azb/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ban/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-be/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bg/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bn/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-br/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bs/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ca/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cak/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ceb/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ckb/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-co/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cs/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cy/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-da/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-de/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-dsb/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-el/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-en-rCA/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-en-rGB/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-eo/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rAR/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rCL/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rES/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rMX/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-et/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-eu/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fa/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ff/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fi/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fr/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fur/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fy-rNL/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ga-rIE/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gd/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gl/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gn/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gu-rIN/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hi-rIN/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hil/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hr/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hsb/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hu/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hy-rAM/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ia/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-in/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-is/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-it/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-iw/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ja/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ka/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kaa/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kab/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kk/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kmr/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kn/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ko/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lij/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lo/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lt/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-mix/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ml/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-mr/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-my/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nb-rNO/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ne-rNP/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nl/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nn-rNO/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-oc/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-or/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pa-rIN/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pa-rPK/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pl/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pt-rBR/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pt-rPT/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-rm/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ro/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ru/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sat/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sc/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-si/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sk/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-skr/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sl/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sq/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sr/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-su/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sv-rSE/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ta/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-te/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tg/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-th/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tl/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tr/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-trs/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tt/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tzm/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ug/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-uk/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ur/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-uz/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-vec/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-vi/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-yo/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-zh-rCN/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values-zh-rTW/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values/colors.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values/strings.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/main/res/values/style.xml create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFeatureTest.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFragmentTest.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/DefaultSelectionActionDelegateTest.kt create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 mobile/android/android-components/components/feature/contextmenu/src/test/resources/robolectric.properties (limited to 'mobile/android/android-components/components/feature/contextmenu') diff --git a/mobile/android/android-components/components/feature/contextmenu/README.md b/mobile/android/android-components/components/feature/contextmenu/README.md new file mode 100644 index 0000000000..6c1983139c --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/README.md @@ -0,0 +1,92 @@ +# [Android Components](../../../README.md) > Feature > Context Menu + +A component for displaying context menus when *long-pressing* web content. + +## Usage + +### Setting up the dependency + +Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)): + +```Groovy +implementation "org.mozilla.components:feature-contextmenu:{latest-version}" +``` + +### Integration + +`ContextMenuFeature` subscribes to the selected `Session` automatically and displays context menus when web content is `long-pressed`. + +Initializing the feature in a [Fragment](https://developer.android.com/reference/androidx/fragment/app/Fragment) (`onViewCreated`) or in an [Activity](https://developer.android.com/reference/android/app/Activity) (`onCreate`): + +```Kotlin +contextMenuFeature = ContextMenuFeature( + fragmentManager, + sessionManager, + + // Use default context menu items: + ContextMenuCandidate.defaultCandidates(context, tabsUseCases, snackbarParentView) +) +``` + +### Forwarding lifecycle events + +Start/Stop events need to be forwarded to the feature: + +```Kotlin +// From onStart(): +feature.start() + +// From onStop(): +feature.stop() +``` + +### Customizing context menu items + +When initializing the feature a list of `ContextMenuCandidate` objects need to be passed to the feature. Instead of using the default list (`ContextMenuCandidate.defaultCandidates()`) a customized list can be passed to the feature. + +For every observed `HitResult` (`Session.Observer.onLongPress()`) the feature will query all candidates (`ContextMenuCandidate.showFor()`) in order to determine which candidates want to show up in the context menu. If a context menu item was selected by the user the feature will invoke the `ContextMenuCandidate.action()` method of the related candidate. + +`ContextMenuCandidate` contains methods (`create*()`) for creating a variety of standard context menu items that can be used when customizing the list. + +```Kotlin +val customCandidates = listOf( + // Item from the list of standard items + ContextMenuCandidate.createOpenInNewTabCandidate(context, tabsUseCases), + + // Custom item + object : ContextMenuCandidate( + id = "org.mozilla.custom.contextmenu.toast", + label = "Show a toast", + showFor = { session, hitResult -> hitResult.src.isNotEmpty() }, + action = { session, hitResult -> + Toast.makeText(context, hitResult.src, Toast.LENGTH_SHORT).show() + } + ) +) + +contextMenuFeature = ContextMenuFeature( + fragmentManager, + sessionManager, + customCandidates) +``` + +## Facts + +This component emits the following [Facts](../../support/base/README.md#Facts): + +| Action | Item | Extras | Description | +|--------|---------|----------------|------------------------------------------| +| CLICK | item | `itemExtras` | The user clicked on a context menu item. | + + +#### `itemExtras` + +| Key | Type | Value | +|--------------|---------|--------------------------------------------| +| item | String | The `id` of the menu item that was clicked | + +## License + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/ diff --git a/mobile/android/android-components/components/feature/contextmenu/build.gradle b/mobile/android/android-components/components/feature/contextmenu/build.gradle new file mode 100644 index 0000000000..f1ab6e0b64 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/build.gradle @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + defaultConfig { + minSdkVersion config.minSdkVersion + compileSdk config.compileSdkVersion + targetSdkVersion config.targetSdkVersion + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + namespace 'mozilla.components.feature.contextmenu' +} + +tasks.withType(KotlinCompile).configureEach { + kotlinOptions.freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi" +} + +dependencies { + implementation project(":browser-state") + implementation project(':concept-engine') + implementation project(':feature-tabs') + implementation project(':feature-app-links') + implementation project(':browser-state') + implementation project(':support-utils') + implementation project(':support-ktx') + implementation project(':feature-search') + implementation project(':ui-widgets') + + implementation ComponentsDependencies.google_material + implementation ComponentsDependencies.androidx_constraintlayout + + testImplementation ComponentsDependencies.androidx_test_core + testImplementation ComponentsDependencies.androidx_test_junit + testImplementation ComponentsDependencies.testing_coroutines + testImplementation ComponentsDependencies.testing_robolectric + + testImplementation project(':support-test') + testImplementation project(':support-test-libstate') +} + +apply from: '../../../android-lint.gradle' +apply from: '../../../publish.gradle' +ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) diff --git a/mobile/android/android-components/components/feature/contextmenu/proguard-rules.pro b/mobile/android/android-components/components/feature/contextmenu/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/AndroidManifest.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e16cda1d34 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt new file mode 100644 index 0000000000..47e3b9c037 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt @@ -0,0 +1,689 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.view.View +import androidx.annotation.VisibleForTesting +import com.google.android.material.snackbar.Snackbar +import mozilla.components.browser.state.state.SessionState +import mozilla.components.browser.state.state.content.DownloadState +import mozilla.components.browser.state.state.content.ShareInternetResourceState +import mozilla.components.concept.engine.HitResult +import mozilla.components.feature.app.links.AppLinksUseCases +import mozilla.components.feature.contextmenu.ContextMenuCandidate.Companion.MAX_TITLE_LENGTH +import mozilla.components.feature.tabs.TabsUseCases +import mozilla.components.support.ktx.android.content.addContact +import mozilla.components.support.ktx.android.content.createChooserExcludingCurrentApp +import mozilla.components.support.ktx.android.content.share +import mozilla.components.support.ktx.kotlin.stripMailToProtocol +import mozilla.components.support.ktx.kotlin.takeOrReplace +import mozilla.components.ui.widgets.DefaultSnackbarDelegate +import mozilla.components.ui.widgets.SnackbarDelegate + +/** + * A candidate for an item to be displayed in the context menu. + * + * @property id A unique ID that will be used to uniquely identify the candidate that the user selected. + * @property label The label that will be displayed in the context menu + * @property showFor If this lambda returns true for a given [SessionState] and [HitResult] then it + * will be displayed in the context menu. + * @property action The action to be invoked once the user selects this item. + */ +data class ContextMenuCandidate( + val id: String, + val label: String, + val showFor: (SessionState, HitResult) -> Boolean, + val action: (SessionState, HitResult) -> Unit, +) { + companion object { + // This is used for limiting image title, in order to prevent crashes caused by base64 encoded image + // https://github.com/mozilla-mobile/android-components/issues/8298 + const val MAX_TITLE_LENGTH = 2500 + + /** + * Returns the default list of context menu candidates. + * + * Use this list if you do not intend to customize the context menu items to be displayed. + */ + fun defaultCandidates( + context: Context, + tabsUseCases: TabsUseCases, + contextMenuUseCases: ContextMenuUseCases, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + ): List = listOf( + createOpenInNewTabCandidate( + context, + tabsUseCases, + snackBarParentView, + snackbarDelegate, + ), + createOpenInPrivateTabCandidate( + context, + tabsUseCases, + snackBarParentView, + snackbarDelegate, + ), + createCopyLinkCandidate(context, snackBarParentView, snackbarDelegate), + createDownloadLinkCandidate(context, contextMenuUseCases), + createShareLinkCandidate(context), + createShareImageCandidate(context, contextMenuUseCases), + createOpenImageInNewTabCandidate( + context, + tabsUseCases, + snackBarParentView, + snackbarDelegate, + ), + createCopyImageCandidate( + context, + contextMenuUseCases, + ), + createSaveImageCandidate(context, contextMenuUseCases), + createSaveVideoAudioCandidate(context, contextMenuUseCases), + createCopyImageLocationCandidate(context, snackBarParentView, snackbarDelegate), + createAddContactCandidate(context), + createShareEmailAddressCandidate(context), + createCopyEmailAddressCandidate(context, snackBarParentView, snackbarDelegate), + ) + + /** + * Context Menu item: "Open Link in New Tab". + * + * @param context [Context] used for various system interactions. + * @param tabsUseCases [TabsUseCases] used for adding new tabs. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createOpenInNewTabCandidate( + context: Context, + tabsUseCases: TabsUseCases, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.open_in_new_tab", + label = context.getString(R.string.mozac_feature_contextmenu_open_link_in_new_tab), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isHttpLink() && + !tab.content.private && + additionalValidation(tab, hitResult) + }, + action = { parent, hitResult -> + val tab = tabsUseCases.addTab( + hitResult.getLink(), + selectTab = false, + startLoading = true, + parentId = parent.id, + contextId = parent.contextId, + ) + + snackbarDelegate.show( + snackBarParentView = snackBarParentView, + text = R.string.mozac_feature_contextmenu_snackbar_new_tab_opened, + duration = Snackbar.LENGTH_LONG, + action = R.string.mozac_feature_contextmenu_snackbar_action_switch, + ) { + tabsUseCases.selectTab(tab) + } + }, + ) + + /** + * Context Menu item: "Open Link in Private Tab". + * + * @param context [Context] used for various system interactions. + * @param tabsUseCases [TabsUseCases] used for adding new tabs. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. */ + fun createOpenInPrivateTabCandidate( + context: Context, + tabsUseCases: TabsUseCases, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.open_in_private_tab", + label = context.getString(R.string.mozac_feature_contextmenu_open_link_in_private_tab), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isHttpLink() && + additionalValidation(tab, hitResult) + }, + action = { parent, hitResult -> + val tab = tabsUseCases.addTab( + hitResult.getLink(), + selectTab = false, + startLoading = true, + parentId = parent.id, + private = true, + ) + + snackbarDelegate.show( + snackBarParentView, + R.string.mozac_feature_contextmenu_snackbar_new_private_tab_opened, + Snackbar.LENGTH_LONG, + R.string.mozac_feature_contextmenu_snackbar_action_switch, + ) { + tabsUseCases.selectTab(tab) + } + }, + ) + + /** + * Context Menu item: "Open Link in external App". + * + * @param context [Context] used for various system interactions. + * @param appLinksUseCases [AppLinksUseCases] used to interact with urls that can be opened in 3rd party apps. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createOpenInExternalAppCandidate( + context: Context, + appLinksUseCases: AppLinksUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.open_in_external_app", + label = context.getString(R.string.mozac_feature_contextmenu_open_link_in_external_app), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.canOpenInExternalApp(appLinksUseCases) && + additionalValidation(tab, hitResult) + }, + action = { _, hitResult -> + val link = hitResult.getLink() + val redirect = appLinksUseCases.appLinkRedirectIncludeInstall(link) + val appIntent = redirect.appIntent + val marketPlaceIntent = redirect.marketplaceIntent + if (appIntent != null) { + appLinksUseCases.openAppLink(appIntent) + } else if (marketPlaceIntent != null) { + appLinksUseCases.openAppLink(marketPlaceIntent) + } + }, + ) + + /** + * Context Menu item: "Add to contact". + * + * @param context [Context] used for various system interactions. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createAddContactCandidate( + context: Context, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.add_to_contact", + label = context.getString(R.string.mozac_feature_contextmenu_add_to_contact), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isMailto() && + additionalValidation(tab, hitResult) + }, + action = { _, hitResult -> context.addContact(hitResult.getLink().stripMailToProtocol()) }, + ) + + /** + * Context Menu item: "Share email address". + * + * @param context [Context] used for various system interactions. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createShareEmailAddressCandidate( + context: Context, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.share_email", + label = context.getString(R.string.mozac_feature_contextmenu_share_email_address), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isMailto() && + additionalValidation(tab, hitResult) + }, + action = { _, hitResult -> context.share(hitResult.getLink().stripMailToProtocol()) }, + ) + + /** + * Context Menu item: "Copy email address". + * + * @param context [Context] used for various system interactions. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createCopyEmailAddressCandidate( + context: Context, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.copy_email_address", + label = context.getString(R.string.mozac_feature_contextmenu_copy_email_address), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isMailto() && + additionalValidation(tab, hitResult) + }, + action = { _, hitResult -> + val email = hitResult.getLink().stripMailToProtocol() + clipPlainText( + context, + email, + email, + R.string.mozac_feature_contextmenu_snackbar_email_address_copied, + snackBarParentView, + snackbarDelegate, + ) + }, + ) + + /** + * Context Menu item: "Open Image in New Tab". + * + * @param context [Context] used for various system interactions. + * @param tabsUseCases [TabsUseCases] used for adding new tabs. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createOpenImageInNewTabCandidate( + context: Context, + tabsUseCases: TabsUseCases, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.open_image_in_new_tab", + label = context.getString(R.string.mozac_feature_contextmenu_open_image_in_new_tab), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isImage() && + additionalValidation(tab, hitResult) + }, + action = { parent, hitResult -> + val tab = tabsUseCases.addTab( + hitResult.src, + selectTab = false, + startLoading = true, + parentId = parent.id, + contextId = parent.contextId, + private = parent.content.private, + ) + + snackbarDelegate.show( + snackBarParentView = snackBarParentView, + text = R.string.mozac_feature_contextmenu_snackbar_new_tab_opened, + duration = Snackbar.LENGTH_LONG, + action = R.string.mozac_feature_contextmenu_snackbar_action_switch, + ) { + tabsUseCases.selectTab(tab) + } + }, + ) + + /** + * Context Menu item: "Save image". + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createSaveImageCandidate( + context: Context, + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.save_image", + label = context.getString(R.string.mozac_feature_contextmenu_save_image), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isImage() && + additionalValidation(tab, hitResult) + }, + action = { tab, hitResult -> + contextMenuUseCases.injectDownload( + tab.id, + DownloadState( + hitResult.src, + skipConfirmation = true, + private = tab.content.private, + referrerUrl = tab.content.url, + ), + ) + }, + ) + + /** + * Context Menu item: "Copy image". + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createCopyImageCandidate( + context: Context, + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.copy_image", + label = context.getString(R.string.mozac_feature_contextmenu_copy_image), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isImage() && + additionalValidation(tab, hitResult) + }, + action = { tab, hitResult -> + contextMenuUseCases.injectCopyFromInternet( + tab.id, + ShareInternetResourceState( + url = hitResult.src, + private = tab.content.private, + referrerUrl = tab.content.url, + ), + ) + }, + ) + + /** + * Context Menu item: "Save video". + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createSaveVideoAudioCandidate( + context: Context, + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.save_video", + label = context.getString(R.string.mozac_feature_contextmenu_save_file_to_device), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isVideoAudio() && + additionalValidation(tab, hitResult) + }, + action = { tab, hitResult -> + contextMenuUseCases.injectDownload( + tab.id, + DownloadState( + hitResult.src, + skipConfirmation = true, + private = tab.content.private, + referrerUrl = tab.content.url, + ), + ) + }, + ) + + /** + * Context Menu item: "Save link". + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createDownloadLinkCandidate( + context: Context, + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.download_link", + label = context.getString(R.string.mozac_feature_contextmenu_download_link), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isLinkForOtherThanWebpage() && + additionalValidation(tab, hitResult) + }, + action = { tab, hitResult -> + contextMenuUseCases.injectDownload( + tab.id, + DownloadState( + hitResult.getLink(), + skipConfirmation = true, + private = tab.content.private, + referrerUrl = tab.content.url, + ), + ) + }, + ) + + /** + * Context Menu item: "Share Link". + * + * @param context [Context] used for various system interactions. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createShareLinkCandidate( + context: Context, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.share_link", + label = context.getString(R.string.mozac_feature_contextmenu_share_link), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + (hitResult.isUri() || hitResult.isImage() || hitResult.isVideoAudio()) && + additionalValidation(tab, hitResult) + }, + action = { _, hitResult -> + val intent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + flags = Intent.FLAG_ACTIVITY_NEW_TASK + putExtra(Intent.EXTRA_TEXT, hitResult.getLink()) + } + context.startActivity( + intent.createChooserExcludingCurrentApp( + context, + context.getString(R.string.mozac_feature_contextmenu_share_link), + ), + ) + }, + ) + + /** + * Context Menu item: "Share image" + * + * @param context [Context] used for various system interactions. + * @param contextMenuUseCases [ContextMenuUseCases] used to integrate other features. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createShareImageCandidate( + context: Context, + contextMenuUseCases: ContextMenuUseCases, + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.share_image", + label = context.getString(R.string.mozac_feature_contextmenu_share_image), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isImage() && + additionalValidation(tab, hitResult) + }, + action = { tab, hitResult -> + contextMenuUseCases.injectShareFromInternet( + tab.id, + ShareInternetResourceState( + url = hitResult.src, + private = tab.content.private, + referrerUrl = tab.content.url, + ), + ) + }, + ) + + /** + * Context Menu item: "Copy Link". + * + * @param context [Context] used for various system interactions. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createCopyLinkCandidate( + context: Context, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.copy_link", + label = context.getString(R.string.mozac_feature_contextmenu_copy_link), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + (hitResult.isUri() || hitResult.isImage() || hitResult.isVideoAudio()) && + additionalValidation(tab, hitResult) + }, + action = { _, hitResult -> + clipPlainText( + context, + hitResult.getLink(), + hitResult.getLink(), + R.string.mozac_feature_contextmenu_snackbar_link_copied, + snackBarParentView, + snackbarDelegate, + ) + }, + ) + + /** + * Context Menu item: "Copy Image Location". + * + * @param context [Context] used for various system interactions. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createCopyImageLocationCandidate( + context: Context, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.copy_image_location", + label = context.getString(R.string.mozac_feature_contextmenu_copy_image_location), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isImage() && + additionalValidation(tab, hitResult) + }, + action = { _, hitResult -> + clipPlainText( + context, + hitResult.getLink(), + hitResult.src, + R.string.mozac_feature_contextmenu_snackbar_link_copied, + snackBarParentView, + snackbarDelegate, + ) + }, + ) + + private fun clipPlainText( + context: Context, + label: String, + plainText: String, + displayTextId: Int, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + ) { + val clipboardManager = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText(label, plainText) + clipboardManager.setPrimaryClip(clip) + + snackbarDelegate.show( + snackBarParentView = snackBarParentView, + text = displayTextId, + duration = Snackbar.LENGTH_SHORT, + ) + } + } +} + +// Some helper methods to work with HitResult. We may want to improve the API of HitResult and remove some of the +// helpers eventually: https://github.com/mozilla-mobile/android-components/issues/1443 + +private fun HitResult.isImage(): Boolean = + (this is HitResult.IMAGE || this is HitResult.IMAGE_SRC) && src.isNotEmpty() + +private fun HitResult.isVideoAudio(): Boolean = + (this is HitResult.VIDEO || this is HitResult.AUDIO) && src.isNotEmpty() + +private fun HitResult.isUri(): Boolean = + ((this is HitResult.UNKNOWN && src.isNotEmpty()) || this is HitResult.IMAGE_SRC) + +private fun HitResult.isHttpLink(): Boolean = + isUri() && getLink().startsWith("http") + +private fun HitResult.isLinkForOtherThanWebpage(): Boolean { + val link = getLink() + val isHtml = link.endsWith("html") || link.endsWith("htm") + return isHttpLink() && !isHtml +} + +private fun HitResult.isIntent(): Boolean = + ( + this is HitResult.UNKNOWN && src.isNotEmpty() && + getLink().startsWith("intent:") + ) + +private fun HitResult.isMailto(): Boolean = + (this is HitResult.UNKNOWN && src.isNotEmpty()) && + getLink().startsWith("mailto:") + +private fun HitResult.canOpenInExternalApp(appLinksUseCases: AppLinksUseCases): Boolean { + if (isHttpLink() || isIntent() || isVideoAudio()) { + val redirect = appLinksUseCases.appLinkRedirectIncludeInstall(getLink()) + return redirect.hasExternalApp() || redirect.hasMarketplaceIntent() + } + return false +} + +internal fun HitResult.getLink(): String = when (this) { + is HitResult.UNKNOWN -> src + is HitResult.IMAGE_SRC -> uri + is HitResult.IMAGE -> + if (title.isNullOrBlank()) { + src.takeOrReplace(MAX_TITLE_LENGTH, "image") + } else { + title.toString() + } + is HitResult.VIDEO -> + if (title.isNullOrBlank()) src else title.toString() + is HitResult.AUDIO -> + if (title.isNullOrBlank()) src else title.toString() + else -> "about:blank" +} + +@VisibleForTesting +internal fun SessionState.isUrlSchemeAllowed(url: String): Boolean { + return when (val engineSession = engineState.engineSession) { + null -> true + else -> { + val urlScheme = Uri.parse(url).normalizeScheme().scheme + !engineSession.getBlockedSchemes().contains(urlScheme) + } + } +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuFeature.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuFeature.kt new file mode 100644 index 0000000000..5c391463d4 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuFeature.kt @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import android.view.HapticFeedbackConstants +import androidx.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting.Companion.PRIVATE +import androidx.fragment.app.FragmentManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.map +import mozilla.components.browser.state.selector.findTabOrCustomTab +import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab +import mozilla.components.browser.state.state.SessionState +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.EngineView +import mozilla.components.concept.engine.HitResult +import mozilla.components.feature.contextmenu.facts.emitCancelMenuFact +import mozilla.components.feature.contextmenu.facts.emitClickFact +import mozilla.components.feature.contextmenu.facts.emitDisplayFact +import mozilla.components.lib.state.ext.flowScoped +import mozilla.components.support.base.feature.LifecycleAwareFeature + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal const val FRAGMENT_TAG = "mozac_feature_contextmenu_dialog" + +/** + * Feature for displaying a context menu after long-pressing web content. + * + * This feature will subscribe to the currently selected tab and display a context menu based on + * the [HitResult] in its `ContentState`. Once the context menu is closed or the user selects an + * item from the context menu the related [HitResult] will be consumed. + * + * @property fragmentManager The [FragmentManager] to be used when displaying a context menu (fragment). + * @property store The [BrowserStore] this feature should subscribe to. + * @property candidates A list of [ContextMenuCandidate] objects. For every observed [HitResult] this feature will query + * all candidates ([ContextMenuCandidate.showFor]) in order to determine which candidates want to show up in the context + * menu. If a context menu item was selected by the user the feature will invoke the [ContextMenuCandidate.action] + * method of the related candidate. + * @property engineView The [EngineView]] this feature component should show context menus for. + * @param tabId Optional id of a tab. Instead of showing context menus for the currently selected tab this feature will + * show only context menus for this tab if an id is provided. + * @param additionalNote which it will be attached to the bottom of context menu but for a specific [HitResult] + */ +class ContextMenuFeature( + private val fragmentManager: FragmentManager, + private val store: BrowserStore, + private val candidates: List, + private val engineView: EngineView, + private val useCases: ContextMenuUseCases, + private val tabId: String? = null, + private val additionalNote: (HitResult) -> String? = { null }, +) : LifecycleAwareFeature { + private var scope: CoroutineScope? = null + + /** + * Start observing the selected session and when needed show a context menu. + */ + override fun start() { + scope = store.flowScoped { flow -> + flow.map { state -> state.findTabOrCustomTabOrSelectedTab(tabId) } + .distinctUntilChangedBy { it?.content?.hitResult } + .collect { state -> + val hitResult = state?.content?.hitResult + if (hitResult != null) { + showContextMenu(state, hitResult) + } else { + hideContextMenu() + } + } + } + } + + /** + * Stop observing the selected session and do not show any context menus anymore. + */ + override fun stop() { + scope?.cancel() + } + + @VisibleForTesting(otherwise = PRIVATE) + internal fun showContextMenu(tab: SessionState, hitResult: HitResult) { + fragmentManager.findFragmentByTag(FRAGMENT_TAG)?.let { fragment -> + // There's already a ContextMenuFragment being displayed. Let's only make sure it has + // a reference to this feature instance. + (fragment as ContextMenuFragment).feature = this + return + } + + val (ids, labels) = candidates + .filter { candidate -> candidate.showFor(tab, hitResult) } + .fold(Pair(mutableListOf(), mutableListOf())) { items, candidate -> + items.first.add(candidate.id) + items.second.add(candidate.label) + items + } + + // We have no context menu items to show for this HitResult. Let's consume it to remove it from the Session. + if (ids.isEmpty()) { + useCases.consumeHitResult(tab.id) + return + } + + // We know that we are going to show a context menu. Now is the time to perform the haptic feedback. + engineView.asView().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + + val fragment = ContextMenuFragment.create(tab, hitResult.getLink(), ids, labels, additionalNote(hitResult)) + fragment.feature = this + emitDisplayFact(labels.joinToString()) + fragment.show(fragmentManager, FRAGMENT_TAG) + } + + private fun hideContextMenu() { + emitCancelMenuFact() + fragmentManager.findFragmentByTag(FRAGMENT_TAG)?.let { fragment -> + fragmentManager.beginTransaction() + .remove(fragment) + .commitAllowingStateLoss() + } + } + + internal fun onMenuItemSelected(tabId: String, itemId: String) { + val tab = store.state.findTabOrCustomTab(tabId) ?: return + val candidate = candidates.find { it.id == itemId } ?: return + + useCases.consumeHitResult(tab.id) + + tab.content.hitResult?.let { hitResult -> + candidate.action.invoke(tab, hitResult) + emitClickFact(candidate) + } + } + + internal fun onMenuCancelled(tabId: String) { + useCases.consumeHitResult(tabId) + } +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuFragment.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuFragment.kt new file mode 100644 index 0000000000..e585aa2c4f --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuFragment.kt @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.DialogInterface +import android.os.Build +import android.os.Bundle +import android.text.Html +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.text.HtmlCompat +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.textview.MaterialTextView +import mozilla.components.browser.state.state.SessionState + +private const val EXPANDED_TITLE_MAX_LINES = 15 +private const val KEY_TITLE = "title" +private const val KEY_SESSION_ID = "session_id" +private const val KEY_IDS = "ids" +private const val KEY_LABELS = "labels" +private const val KEY_ADDITIONAL_NOTE = "additional_note" + +/** + * [DialogFragment] implementation to display the actual context menu dialog. + */ +class ContextMenuFragment : DialogFragment() { + internal var feature: ContextMenuFeature? = null + + @VisibleForTesting internal val itemIds: List by lazy { + requireArguments().getStringArrayList(KEY_IDS)!! + } + + @VisibleForTesting internal val itemLabels: List by lazy { + requireArguments().getStringArrayList(KEY_LABELS)!! + } + + @VisibleForTesting internal val sessionId: String by lazy { + requireArguments().getString(KEY_SESSION_ID)!! + } + + @VisibleForTesting internal val title: String by lazy { + requireArguments().getString(KEY_TITLE)!! + } + + @VisibleForTesting internal val additionalNote: String? by lazy { + requireArguments().getString(KEY_ADDITIONAL_NOTE) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + @SuppressLint("UseGetLayoutInflater") + val inflater = LayoutInflater.from(requireContext()) + + val builder = AlertDialog.Builder(requireContext()) + .setCustomTitle(createDialogTitleView(inflater)) + .setView(createDialogContentView(inflater)) + + return builder.create() + } + + @SuppressLint("InflateParams") + internal fun createDialogTitleView(inflater: LayoutInflater): View { + return inflater.inflate( + R.layout.mozac_feature_contextmenu_title, + null, + ).findViewById( + R.id.titleView, + ).apply { + text = title + + setOnClickListener { + maxLines = EXPANDED_TITLE_MAX_LINES + } + } + } + + @SuppressLint("InflateParams") + internal fun createDialogContentView(inflater: LayoutInflater): View { + val view = inflater.inflate(R.layout.mozac_feature_contextmenu_dialog, null) + + view.findViewById(R.id.recyclerView).apply { + layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + adapter = ContextMenuAdapter(this@ContextMenuFragment, inflater) + } + + additionalNote?.let { value -> + val additionalNoteView = view.findViewById(R.id.additional_note) + additionalNoteView.visibility = View.VISIBLE + additionalNoteView.text = getSpannedValueOfString(value) + } + + return view + } + + private fun getSpannedValueOfString(value: String) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + Html.fromHtml(value, HtmlCompat.FROM_HTML_MODE_LEGACY) + } else { + @Suppress("Deprecation") + Html.fromHtml(value) + } + + internal fun onItemSelected(position: Int) { + feature?.onMenuItemSelected(sessionId, itemIds[position]) + + dismiss() + } + + override fun onCancel(dialog: DialogInterface) { + feature?.onMenuCancelled(sessionId) + } + + companion object { + /** + * Create a new [ContextMenuFragment]. + */ + fun create( + tab: SessionState, + title: String, + ids: List, + labels: List, + additionalNote: String?, + ): ContextMenuFragment { + val arguments = Bundle() + arguments.putString(KEY_TITLE, title) + arguments.putStringArrayList(KEY_IDS, ArrayList(ids)) + arguments.putStringArrayList(KEY_LABELS, ArrayList(labels)) + arguments.putString(KEY_SESSION_ID, tab.id) + arguments.putString(KEY_ADDITIONAL_NOTE, additionalNote) + + val fragment = ContextMenuFragment() + fragment.arguments = arguments + return fragment + } + } +} + +/** + * RecyclerView adapter for displaying the context menu. + */ +internal class ContextMenuAdapter( + private val fragment: ContextMenuFragment, + private val inflater: LayoutInflater, +) : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, position: Int) = ContextMenuViewHolder( + inflater.inflate(R.layout.mozac_feature_contextmenu_item, parent, false), + ) + + override fun getItemCount(): Int = fragment.itemIds.size + + override fun onBindViewHolder(holder: ContextMenuViewHolder, position: Int) { + val label = fragment.itemLabels[position] + holder.labelView.text = label + + holder.itemView.setOnClickListener { fragment.onItemSelected(position) } + } +} + +/** + * View holder for a context menu item. + */ +internal class ContextMenuViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + internal val labelView = itemView.findViewById(R.id.labelView) +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuUseCases.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuUseCases.kt new file mode 100644 index 0000000000..a523917221 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuUseCases.kt @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import mozilla.components.browser.state.action.ContentAction +import mozilla.components.browser.state.action.CopyInternetResourceAction +import mozilla.components.browser.state.action.ShareInternetResourceAction +import mozilla.components.browser.state.state.content.DownloadState +import mozilla.components.browser.state.state.content.ShareInternetResourceState +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.HitResult + +/** + * Contains use cases related to the context menu feature. + * + * @param store the application's [BrowserStore]. + */ +class ContextMenuUseCases( + store: BrowserStore, +) { + class ConsumeHitResultUseCase( + private val store: BrowserStore, + ) { + /** + * Consumes the [HitResult] from the [BrowserStore] with the given [tabId]. + */ + operator fun invoke(tabId: String) { + store.dispatch(ContentAction.ConsumeHitResultAction(tabId)) + } + } + + class InjectDownloadUseCase( + private val store: BrowserStore, + ) { + /** + * Adds a [DownloadState] to the [BrowserStore] with the given [tabId]. + * + * This is a hacky workaround. After we have migrated everything from browser-session to + * browser-state we should revisits this and find a better solution. + */ + operator fun invoke(tabId: String, download: DownloadState) { + store.dispatch( + ContentAction.UpdateDownloadAction( + tabId, + download, + ), + ) + } + } + + /** + * Usecase allowing adding a new 'share' [ShareInternetResourceState] to the [BrowserStore] + */ + class InjectShareInternetResourceUseCase( + private val store: BrowserStore, + ) { + /** + * Adds a specific [ShareInternetResourceState] to be shared to the [BrowserStore]. + */ + operator fun invoke(tabId: String, internetResource: ShareInternetResourceState) { + store.dispatch(ShareInternetResourceAction.AddShareAction(tabId, internetResource)) + } + } + + /** + * Use case allowing adding a new 'copy' [ShareInternetResourceState] to the [BrowserStore] + */ + class InjectCopyInternetResourceUseCase( + private val store: BrowserStore, + ) { + /** + * Adds a specific [ShareInternetResourceState] to be copied to the [BrowserStore]. + */ + operator fun invoke(tabId: String, internetResource: ShareInternetResourceState) { + store.dispatch(CopyInternetResourceAction.AddCopyAction(tabId, internetResource)) + } + } + + val consumeHitResult = ConsumeHitResultUseCase(store) + val injectDownload = InjectDownloadUseCase(store) + val injectShareFromInternet = InjectShareInternetResourceUseCase(store) + val injectCopyFromInternet = InjectCopyInternetResourceUseCase(store) +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/DefaultSelectionActionDelegate.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/DefaultSelectionActionDelegate.kt new file mode 100644 index 0000000000..9380c8ca47 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/DefaultSelectionActionDelegate.kt @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import android.content.res.Resources +import android.util.Patterns +import androidx.annotation.VisibleForTesting +import mozilla.components.concept.engine.selection.SelectionActionDelegate +import mozilla.components.feature.contextmenu.facts.emitTextSelectionClickFact +import mozilla.components.feature.search.SearchAdapter + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal const val SEARCH = "CUSTOM_CONTEXT_MENU_SEARCH" + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal const val SEARCH_PRIVATELY = "CUSTOM_CONTEXT_MENU_SEARCH_PRIVATELY" + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal const val SHARE = "CUSTOM_CONTEXT_MENU_SHARE" + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal const val EMAIL = "CUSTOM_CONTEXT_MENU_EMAIL" + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal const val CALL = "CUSTOM_CONTEXT_MENU_CALL" + +private val customActions = arrayOf(CALL, EMAIL, SEARCH, SEARCH_PRIVATELY, SHARE) + +/** + * Adds normal and private search buttons to text selection context menus. + * Also adds share, email, and call actions which are optionally displayed. + */ +class DefaultSelectionActionDelegate( + private val searchAdapter: SearchAdapter, + resources: Resources, + private val shareTextClicked: ((String) -> Unit)? = null, + private val emailTextClicked: ((String) -> Unit)? = null, + private val callTextClicked: ((String) -> Unit)? = null, + private val actionSorter: ((Array) -> Array)? = null, +) : SelectionActionDelegate { + + private val normalSearchText = + resources.getString(R.string.mozac_selection_context_menu_search_2) + private val privateSearchText = + resources.getString(R.string.mozac_selection_context_menu_search_privately_2) + private val shareText = resources.getString(R.string.mozac_selection_context_menu_share) + private val emailText = resources.getString(R.string.mozac_selection_context_menu_email) + private val callText = resources.getString(R.string.mozac_selection_context_menu_call) + + override fun getAllActions(): Array = customActions + + @SuppressWarnings("ComplexMethod") + override fun isActionAvailable(id: String, selectedText: String): Boolean { + val isPrivate = searchAdapter.isPrivateSession() + return (id == SHARE && shareTextClicked != null) || + ( + id == EMAIL && emailTextClicked != null && + Patterns.EMAIL_ADDRESS.matcher(selectedText.trim()).matches() + ) || + ( + id == CALL && + callTextClicked != null && Patterns.PHONE.matcher(selectedText.trim()).matches() + ) || + (id == SEARCH && !isPrivate) || + (id == SEARCH_PRIVATELY && isPrivate) + } + + override fun getActionTitle(id: String): CharSequence? = when (id) { + SEARCH -> normalSearchText + SEARCH_PRIVATELY -> privateSearchText + SHARE -> shareText + EMAIL -> emailText + CALL -> callText + else -> null + } + + override fun performAction(id: String, selectedText: String): Boolean { + emitTextSelectionClickFact(id) + return when (id) { + SEARCH -> { + searchAdapter.sendSearch(false, selectedText) + true + } + SEARCH_PRIVATELY -> { + searchAdapter.sendSearch(true, selectedText) + true + } + SHARE -> { + shareTextClicked?.invoke(selectedText) + true + } + EMAIL -> { + emailTextClicked?.invoke(selectedText.trim()) + true + } + CALL -> { + callTextClicked?.invoke(selectedText.trim()) + true + } + else -> { + false + } + } + } + + /** + * Takes in a list of actions and sorts them. + * @returns the sorted list. + */ + override fun sortedActions(actions: Array): Array { + return if (actionSorter != null) { + actionSorter.invoke(actions) + } else { + actions + } + } +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ext/DefaultSelectionActionDelegate.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ext/DefaultSelectionActionDelegate.kt new file mode 100644 index 0000000000..b9de9f2d0d --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ext/DefaultSelectionActionDelegate.kt @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu.ext + +import android.content.Context +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.feature.contextmenu.DefaultSelectionActionDelegate +import mozilla.components.feature.search.BrowserStoreSearchAdapter +import mozilla.components.support.ktx.android.content.call +import mozilla.components.support.ktx.android.content.email +import mozilla.components.support.ktx.android.content.share + +/** + * More convenient secondary constructor for creating a [DefaultSelectionActionDelegate]. + */ +@Suppress("FunctionName") +fun DefaultSelectionActionDelegate( + store: BrowserStore, + context: Context, + shareTextClicked: ((String) -> Unit)? = { context.share(it) }, + emailTextClicked: ((String) -> Unit)? = { context.email(it) }, + callTextClicked: ((String) -> Unit)? = { context.call(it) }, +) = + DefaultSelectionActionDelegate( + BrowserStoreSearchAdapter(store), + context.resources, + shareTextClicked, + emailTextClicked, + callTextClicked, + ) diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/facts/ContextMenuFacts.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/facts/ContextMenuFacts.kt new file mode 100644 index 0000000000..55c694ddab --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/facts/ContextMenuFacts.kt @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu.facts + +import mozilla.components.feature.contextmenu.ContextMenuCandidate +import mozilla.components.support.base.Component +import mozilla.components.support.base.facts.Action +import mozilla.components.support.base.facts.Fact +import mozilla.components.support.base.facts.collect + +/** + * Facts emitted for telemetry related to [ContextMenuFeature] + */ +class ContextMenuFacts { + /** + * Items that specify which portion of the [ContextMenuFeature] was interacted with + */ + object Items { + const val MENU = "menu" + const val ITEM = "item" + const val TEXT_SELECTION_OPTION = "textSelectionOption" + } +} + +private fun emitContextMenuFact( + action: Action, + item: String, + value: String? = null, + metadata: Map? = null, +) { + Fact( + Component.FEATURE_CONTEXTMENU, + action, + item, + value, + metadata, + ).collect() +} + +internal fun emitClickFact(candidate: ContextMenuCandidate) { + val metadata = mapOf("item" to candidate.id) + emitContextMenuFact(Action.CLICK, ContextMenuFacts.Items.ITEM, metadata = metadata) +} + +internal fun emitDisplayFact(labels: String) { + emitContextMenuFact(Action.DISPLAY, ContextMenuFacts.Items.MENU, labels) +} + +internal fun emitCancelMenuFact() { + emitContextMenuFact(Action.CANCEL, ContextMenuFacts.Items.MENU) +} + +internal fun emitTextSelectionClickFact(optionId: String) { + val metadata = mapOf("textSelectionOption" to optionId) + emitContextMenuFact(Action.CLICK, ContextMenuFacts.Items.TEXT_SELECTION_OPTION, metadata = metadata) +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_dialog.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_dialog.xml new file mode 100644 index 0000000000..9918f93fe3 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_dialog.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_item.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_item.xml new file mode 100644 index 0000000000..47b6e14706 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_item.xml @@ -0,0 +1,18 @@ + + + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_title.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_title.xml new file mode 100644 index 0000000000..57319ac75c --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/layout/mozac_feature_contextmenu_title.xml @@ -0,0 +1,23 @@ + + + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-am/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-am/strings.xml new file mode 100644 index 0000000000..3e3debaac9 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-am/strings.xml @@ -0,0 +1,53 @@ + + + + አገናኙን በአዲስ ትር ውስጥ ክፈት + + አገናኙን በግል ትር ውስጥ ክፈት + + ምስሉን በአዲስ ትር ውስጥ ክፈት + + አገናኙን አውርድ + + አገናኝ አጋራ + + ምስል አጋራ + + አገናኝ ቅዳ + + የምስሉን መገኛ አገናኝ ቅዳ + + ምስል አስቀምጥ + + ምስል ቅዳ + + ፋይሉን ወደ መሳሪያ አስቀምጥ + + አዲስ ትር ተከፍቷል + + አዲስ የግል ትር ተከፍቷል + + አገናኝ ወደ ቅንጥብ ሰሌዳ ተቀድቷል + + ቀይር + + አገናኙን በሌላ መተግበሪያ ውስጥ ክፈት + + የኢሜይል አድራሻ አጋራ + + የኢሜል አድራሻ ቅዳ + + የኢሜል አድራሻ ወደ ቅንጥብ ሰሌዳ ተቀድቷል + + ወደ እውቂያ አክል + + ፈልግ + + የግል ፍለጋ + + አጋራ + + ኢሜይል + + ደውል + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-an/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-an/strings.xml new file mode 100644 index 0000000000..c9be3f65a0 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-an/strings.xml @@ -0,0 +1,51 @@ + + + + Ubrir lo vinclo en una nueva pestanya + + Ubrir lo vinclo en una pestanya privada + + Ubrir la imachen en una nueva pestanya + + Descargar lo vinclo + + Compartir lo vinclo + + Compartir la imachen + + Copiar lo vinclo + + Copiar l’adreza d’a imachen + + Alzar la imachen + + Alzar la imachen en dispositivo + + S’ha ubierto una nueva pestanya + + S’ha ubierto una nueva pestanya privada + + S’ha copiau lo vinclo en o portafuellas + + Cambiar + + Ubrir lo vinclo en una aplicación externa + + Compartir adreza de correu-e + + Copiar adreza de correu-e + + Adreza de correu copiada ta lo portafuellas + + Anyadir a contacto + + Buscar + + Busqueda privada + + Compartir + + Correu-e + + Gritar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ar/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000000..7f770f9725 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ar/strings.xml @@ -0,0 +1,53 @@ + + + + افتح الرابط في لسان جديد + + افتح الرابط في لسان خاص + + افتح الصورة في لسان جديد + + نزّل الرابط + + شارِك الرابط + + شارك الصورة + + انسخ الرابط + + انسخ مكان الصورة + + احفظ الصورة + + انسخ الصورة + + احفظ الملف في الجهاز + + فُتِح لسان جديد + + فُتِح لسان خاص جديد + + نُسخ الرابط إلى الحافظة + + انتقل + + افتح الرابط في تطبيق خارجي + + شارِك عنوان البريد + + انسخ عنوان البريد الإلكتروني + + نُسخ عنوان البريد إلى الحافظة + + أضِف إلى المتراسلين + + ابحث + + بحث خاص + + شارِك + + أبرِد + + اتصل به + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ast/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ast/strings.xml new file mode 100644 index 0000000000..0bcbede6fd --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ast/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir l\'enllaz nuna llingüeta nueva + + Abrir l\'enllaz nuna llingüeta privada + + Abrir la imaxe nuna llingüeta nueva + + Baxar l\'enllaz + + Compartir l\'enllaz + + Compartir la imaxe + + Copiar l\'enllaz + + Copiar la llocalización de la imaxe + + Guardar la imaxe + + Copiar la imaxe + + Guardar el ficheru nel preséu + + Abrióse una llingüeta nueva + + Abrióse una llingüeta privada nueva + + L\'enllaz copióse al cartafueyu + + Cambiar + + Abrir l\'enllaz nuna aplicación esterna + + Compartir la direición de corréu electrónicu + + Copiar la direición de corréu electrónicu + + La direición de corréu electrónicu copióse al cartafueyu + + Amestar a un contautu + + Buscar + + Busca privada + + Compartir + + Unviar per corréu electrónicu + + Llamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-az/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-az/strings.xml new file mode 100644 index 0000000000..ace251e709 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-az/strings.xml @@ -0,0 +1,51 @@ + + + + Keçidi yeni vərəqdə aç + + Keçidi məxfi vərəqdə aç + + Şəkli yeni vərəqdə aç + + Endirmə keçidi + + Keçidi paylaş + + Şəkli paylaş + + Keçidi köçür + + Şəkil ünvanını köçür + + Şəkli saxla + + Şəkli cihaza saxla + + Yeni vərəq açıldı + + Yeni məxfi vərəq açıldı + + Keçid buferə köçürüldü + + Keç + + Keçidi başqa tətbiqdə aç + + E-poçt ünvanını paylaş + + E-poçt ünvanını köçür + + E-poçt ünvanı mübadilə buferinə köçürüldü + + Əlaqələrə əlavə et + + Axtar + + Məxfi Axtarış + + Paylaş + + E-poçt + + Zəng et + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-azb/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-azb/strings.xml new file mode 100644 index 0000000000..97b092eb5f --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-azb/strings.xml @@ -0,0 +1,53 @@ + + + + باغلانتی‌نی یئنی تاغدا آچ + + باغلانتی‌نی گیزلی تاغدا آچ + + عکسی یئنی تاغدا آچ + + باغلانتی‌نی یئندیر + + باغلانتی‌نی پایلاش + + عکسی پایلاش + + باغلانتی‌نی کوپی ائله + + عکس یئرینی کوپی ائله + + عکسی ساخلا + + عکسی کوپی ائله + + فایلی جهازدا ساخلا + + یئنی تاغ آچیلدی + + یئنی گیزلی تاغ آچیلدی + + باغلانتی کلیپ‌بوردا کوپی اولدو + + بونا گئچ + + باغلانتی‌نی ائشیکده‌کی اپ‌ده آچ + + ایمیل آدرسی پایلاش + + ایمیل آدرسی کوپی ائله + + ایمیل آدرسی کلیپ‌بوردا کوپی اولدو + + موخاطب‌لره آرتیر + + آختاریش + + گیزلی آختاریش + + پایلاش + + ایمیل + + تماس + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ban/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ban/strings.xml new file mode 100644 index 0000000000..a478cc786c --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ban/strings.xml @@ -0,0 +1,21 @@ + + + + Unduh tautan + + Ngbagiang tautan + + Ngbagiang gambar + + Raksa gambar + + Raksa berkas ka piranti + + Tambah ka kontak + + Rereh + + Bagiang + + Rerepél + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-be/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-be/strings.xml new file mode 100644 index 0000000000..06aed70eef --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-be/strings.xml @@ -0,0 +1,53 @@ + + + + Адкрыць спасылку ў новай картцы + + Адкрыць спасылку ў прыватнай картцы + + Адкрыць выяву ў новай картцы + + Сцягнуць спасылку + + Падзяліцца спасылкай + + Падзяліцца выявай + + Скапіраваць спасылку + + Капіяваць адрас выявы + + Захаваць выяву + + Капіяваць відарыс + + Захавайце файл на прыладзе + + Адкрыта новая картка + + Адкрыта новая прыватная картка + + Спасылка скапіявана ў буфер абмену + + Перайсці + + Адкрыць спасылку ў знешняй праграме + + Падзяліцца адрасам эл.пошты + + Капіяваць адрас эл.пошты + + Адрас пошты скапіяваны ў буфер абмену + + Дадаць у кантакт + + Шукаць + + Прыватны пошук + + Падзяліцца + + Эл.пошта + + Выклік + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bg/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000000..816956615f --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bg/strings.xml @@ -0,0 +1,53 @@ + + + + Отваряне в раздел + + Отваряне в поверителен раздел + + Отваряне в раздел + + Изтегляне на препратката + + Споделяне на препратка + + Споделяне на изображение + + Копиране на препратка + + Копиране адреса на изображение + + Запазване на изображение + + Копиране на изображението + + Запазване на файла на устройството + + Отворен е нов раздел + + Отворен е нов поверителен раздел + + Препратката е копирана в буфера + + Отваряне + + Отваряне на препратка в приложение + + Споделяне на електронен адрес + + Копиране на електронен адрес + + Имейлът е копиран в буфера + + Добавяне към контакт + + Търсене + + Поверително търсене + + Споделяне + + Имейл + + Позвъняване + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bn/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bn/strings.xml new file mode 100644 index 0000000000..c5f3a8c7a6 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bn/strings.xml @@ -0,0 +1,51 @@ + + + + একটি নতুন ট্যাবে লিংক খুলুন + + লিংকটি ব্যক্তিগত ট্যাবে খুলুন + + একটি নতুন ট্যাবে ছবিটি খুলুন + + ডাউনলোড লিঙ্ক + + লিংক শেয়ার করুন + + ছবি শেয়ার করুন + + লিংক কপি করুন + + ছবির অবস্থান কপি করুন + + ছবি সংরক্ষণ করুন + + ডিভাইসে ফাইল সংরক্ষণ করুন + + নতুন ট্যাব খোলা হয়েছে + + নতুন ব্যক্তিগত ট্যাব খোলা হয়েছে + + ক্লিপবোর্ডে লিংক কপি করা হয়েছে + + পরিবর্তন + + বাইরের অ্যাপে লিংক খুলুন + + ইমেইল ঠিকানা শেয়ার করো + + ইমেইল ঠিকানা অনুলিপি + + ইমেইল ঠিকানা ক্লিপবোর্ডে অনুলিপি করা হয়েছে + + কন্টাক্টে যোগ করুন + + অনুসন্ধান + + ব্যক্তিগত অনুসন্ধান + + শেয়ার + + ইমেইল + + কল + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-br/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-br/strings.xml new file mode 100644 index 0000000000..92f48be6db --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-br/strings.xml @@ -0,0 +1,53 @@ + + + + Digeriñ an ere e-barzh un ivinell nevez + + Digeriñ an ere en un ivinell brevez + + Digeriñ ar skeudenn e-barzh un ivinell nevez + + Pellgargañ an ere + + Rannañ an ere + + Rannañ ar skeudenn + + Eilañ an ere + + Eilañ lecʼhiadur ar skeudenn + + Enrollañ ar skeudenn + + Eilañ ar skeudenn + + Enrollañ ar restr en trevnad + + Digor eo an ivinell nevez + + Ivinell brevez nevez bet digoret + + Ere eilet er golver + + Mont + + Digeriñ an ere en un arload diavaez + + Rannañ ar chomlec’h postel + + Eilañ ar chomlec’h postel + + Chomlec’h postel eilet er golver + + Ouzhpennañ en darempredoù + + Klask + + Klask prevez + + Rannañ + + Postel + + Gervel + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bs/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bs/strings.xml new file mode 100644 index 0000000000..1a19e0fb69 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-bs/strings.xml @@ -0,0 +1,53 @@ + + + + Otvori link u novom tabu + + Otvori link u privatnom tabu + + Otvori sliku u novom tabu + + Preuzmi link + + Podijeli link + + Podijeli sliku + + Kopiraj link + + Kopiraj lokaciju slike + + Spasi sliku + + Kopiraj sliku + + Spasi fajl na uređaj + + Otvoren novi tab + + Otvoren novi privatni tab + + Link kopiran na clipboard + + Prebaci + + Otvori link u vanjskoj aplikaciji + + Podijeli email adresu + + Kopiraj email adresu + + Email adresa kopirana u clipboard + + Dodaj u kontakt + + Traži + + Privatna pretraga + + Podijeli + + Email + + Nazovi + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ca/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000000..d035c774d9 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ca/strings.xml @@ -0,0 +1,53 @@ + + + + Obre l’enllaç en una pestanya nova + + Obre l’enllaç en una pestanya privada + + Obre la imatge en una pestanya nova + + Baixa l’enllaç + + Comparteix l’enllaç + + Comparteix la imatge + + Copia l’enllaç + + Copia la ubicació de la imatge + + Desa la imatge + + Copia la imatge + + Desa el fitxer al dispositiu + + S’ha obert una pestanya nova + + S’ha obert una pestanya privada nova + + S’ha copiat l’enllaç al porta-retalls + + Vés-hi + + Obre l’enllaç en una aplicació externa + + Comparteix l’adreça electrònica + + Copia l’adreça electrònica + + S’ha copiat l’adreça al porta-retalls + + Afegeix a un contacte + + Cerca + + Cerca privada + + Comparteix + + Correu electrònic + + Truca + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cak/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cak/strings.xml new file mode 100644 index 0000000000..4b0ed88240 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cak/strings.xml @@ -0,0 +1,53 @@ + + + + Tijaq ri ximonel pa jun k\'ak\'a\' ruwi\' + + Tijaq ximöy pa jun ichinan ruwi\' + + Tijaq wachib\'äl pa jun k\'ak\'a\' ruwi\' + + Niqasäx ximonel + + Tikomonïx ri ximöy + + Tikomonïx wachib\'äl + + Tiwachib\'ëx ximonel + + Tiwachib\'ëx rub\'ey wachib\'äl + + Tiyak wachib\'äl + + Tiwachib\'ëx wachib\'äl + + Tityak yakb\'äl pan okisab\'äl + + Xjaq k\'ak\'a\' ruwi\' + + Xjaq k\'ak\'a\' ichinan ruwi\' + + Xwachib\'ëx ri ximonel molwuj + + Tijalwachïx + + Tijaq ximonel pa jun chik chokoy + + Tikomonïx rochochib\'al taqoya\'l + + Tiwachib\'ëx rochochib\'al taqoya\'l + + Taqoya\'l wachib\'en pa molwuj + + Titz\'aqatisäx pa rub\'i\' achib\'il + + Tikanöx + + Ichinan Kanoxïk + + Tikomonïx + + Taqoya\'l + + Toyon + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ceb/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ceb/strings.xml new file mode 100644 index 0000000000..ff3d7d4286 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ceb/strings.xml @@ -0,0 +1,51 @@ + + + + i-Open ang link sa bag-ong tab + + i-Open ang link sa pribadong tab + + i-Open ang image sa bag-ong tab + + i-Download ang link + + i-Share ang link + + i-Share ang image + + Kopyaha ang link + + Kopyaha ang image location + + i-Save ang image + + i-Save ang file sa device + + Bag-ong tab na-open + + Bag-ong private tab na-open + + Ang link na-kopya sa clipboard + + Balhin + + i-Open ang link sa external nga app + + i-Share ang email address + + Kopyaha ang email address + + Ang email address nakopya na sa clipboard + + Idugang sa contact + + Search + + Private Search + + Share + + Email + + Call + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ckb/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ckb/strings.xml new file mode 100644 index 0000000000..1876064ce0 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ckb/strings.xml @@ -0,0 +1,51 @@ + + + + بەستەر بکەرەوە لە بازدەری نوێ + + بەستەر بکەرەوە لە بازدەری تایبەت + + وێنە بکەرەوە لە بازدەری نوێ + + بەستەری داگرتن + + بەستەر بڵاوبکەرەوە + + وێنە بڵاوبکەرەوە + + بەستەر لەبەربگرەوە + + شوێنی وێنە لەبەربگرەوە + + وێنە پاشەکەوت بکە + + پەڕگە پاشەکەوتبکە لە ئامێر + + بازدەری نوێ کرایەوە + + بازدەری تایبەتی نوێ کرایەوە + + بەستەر لەبەرگیراوە بۆ گرتەتەختە + + گۆڕین + + بەستەر لە بەرنامەی دەرەکی بکەرەوە + + پۆستی ئەلیکترۆنی بڵابکەرەوە + + پۆستی ئەلیکترۆنی لەبەربگرەوە + + پۆستی ئەلیکترۆنی لەبەرگیرایەوە لە گرتەتەختە + + زیادی بکە بۆ پەیوەندیکەران + + گەڕان + + گەڕانی تایبەت + + بڵاوکردنەوە + + پۆستی ئەلکترۆنی + + پەیوەندی + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-co/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-co/strings.xml new file mode 100644 index 0000000000..9a04c02cc0 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-co/strings.xml @@ -0,0 +1,53 @@ + + + + Apre u liame in una nova unghjetta + + Apre u liame in una nova unghjetta privata + + Apre a fiura in una nova unghjetta + + Scaricà a destinazione di u liame + + Sparte u liame + + Sparte a fiura + + Cupià u liame + + Cupià l’indirizzu di a fiura + + Arregistrà a fiura + + Cupià a fiura + + Arregistrà u schedariu nant’à l’apparechju + + Nova unghjetta aperta + + Nova unghjetta privata aperta + + Liame cupiatu in u preme’papei + + Affissà + + Apre u liame in un’appiecazione esterna + + Sparte l’indirizzu elettronicu + + Cupià l’indirizzu elettronicu + + L’indirizzu elettronicu hè statu cupiatu in u preme’papei + + Aghjunghje à un cuntattu + + Ricercà + + Ricerca privata + + Sparte + + Mandà un messaghju + + Chjamà + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cs/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000000..65b3397a18 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cs/strings.xml @@ -0,0 +1,53 @@ + + + + Otevřít odkaz v novém panelu + + Otevřít odkaz v anonymním panelu + + Otevřít obrázek v novém panelu + + Stáhnout + + Sdílet odkaz + + Sdílet obrázek + + Kopírovat odkaz + + Kopírovat adresu obrázku + + Uložit obrázek + + Kopírovat obrázek + + Uložit soubor do zařízení + + Nový panel otevřen + + Nový anonymní panel otevřen + + Odkaz zkopírován do schránky + + Přepnout + + Otevřít odkaz v externí aplikaci + + Sdílet e-mailovou adresu + + Kopírovat e-mailovou adresu + + E-mailová adresa byla zkopírována do schránky + + Přidat kontakt + + Vyhledat + + Vyhledat v anonymním okně + + Sdílet + + Poslat e-mailem + + Zavolat + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cy/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cy/strings.xml new file mode 100644 index 0000000000..82df2a9d9b --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-cy/strings.xml @@ -0,0 +1,53 @@ + + + + Agor dolen mewn tab newydd + + Agor dolen mewn tab preifat + + Agor delwedd mewn tab newydd + + Dolen llwytho i lawr + + Rhannu dolen + + Rhannu delwedd + + Copïo dolen + + Copi lleoliad delwedd + + Cadw delwedd + + Copïo delwedd + + Cadw ffeil i’r ddyfais + + Tab newydd wedi ei agor + + Tab preifat newydd wedi ei agor + + Copïwyd dolen i’r clipfwrdd + + Newid + + Agorwch y ddolen mewn ap allanol + + Rhannu cyfeiriad e-bost + + Copïo cyfeiriad e-bost + + Cyfeiriad e-bost wedi ei gopïo i’r clipfwrdd + + Ychwanegu i gyswllt + + Chwilio + + Chwilio Preifat + + Rhannu + + E-bost + + Galw + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-da/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-da/strings.xml new file mode 100644 index 0000000000..fa95f01c68 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-da/strings.xml @@ -0,0 +1,53 @@ + + + + Åbn link i nyt faneblad + + Åbn link i privat faneblad + + Åbn billede i nyt faneblad + + Hent link + + Del link + + Del billede + + Kopier link + + Kopier billedadresse + + Gem billede + + Kopier billede + + Gem fil på enheden + + Nyt faneblad er åbnet + + Et nyt privat faneblad blev åbnet + + Link kopieret til udklipsholder + + Skift + + Åbn link i en ekstern app + + Del mailadresse + + Kopier mailadresse + + Mailadresse kopieret til udklipsholder + + Føj til kontakt + + Søg + + Privat søgning + + Del + + Send mail + + Ring + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-de/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-de/strings.xml new file mode 100644 index 0000000000..63f2e62097 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-de/strings.xml @@ -0,0 +1,62 @@ + + + + Link in neuem Tab öffnen + + + Link in privatem Tab öffnen + + + Grafik in neuem Tab öffnen + + + Link herunterladen + + Link teilen + + + Grafik teilen + + Link kopieren + + + Grafikadresse kopieren + + + Grafik speichern + + + Grafik kopieren + + Datei auf Gerät speichern + + Neuer Tab geöffnet + + + Neuer privater Tab geöffnet + + + Link in Zwischenablage kopiert + + Wechseln + + Link in externer App öffnen + + E-Mail-Adresse teilen + + E-Mail-Adresse kopieren + + E-Mail-Adresse in Zwischenablage kopiert + + Zu Kontakt hinzufügen + + Suchen + + Private Suche + + Teilen + + Per E-Mail versenden + + Anrufen + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-dsb/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-dsb/strings.xml new file mode 100644 index 0000000000..3ab0968145 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-dsb/strings.xml @@ -0,0 +1,53 @@ + + + + Wótkaz w nowem rejtariku wócyniś + + Wótkaz w priwatnem rejtariku wócyniś + + Wobraz w nowem rejtariku wócyniś + + Ześěgnjeński wótkaz + + Wótkaz źěliś + + Wobraz źěliś + + Wótkaz kopěrowaś + + Wobrazowu adresu kopěrowaś + + Wobraz składowaś + + Wobraz kopěrowaś + + Na rěźe składowaś + + Nowy rejtarik jo se wócynił + + Nowy priwatny rejtarik jo se wócynił + + Wótkaz jo se do mjazywótkłada kopěrował + + Pśešaltowaś + + Wótkaz w eksternem nałoženju wócyniś + + E-mailowu adresu źěliś + + E-mailowu adresu kopěrowaś + + E-mailowa adresa jo se kopěrowała do mjazywótkłada + + Kontaktoju pśidaś + + Pytaś + + Priwatne pytanje + + Źěliś + + E-mail + + Wołaś + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-el/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-el/strings.xml new file mode 100644 index 0000000000..5a6f77c3ba --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-el/strings.xml @@ -0,0 +1,53 @@ + + + + Άνοιγμα συνδέσμου σε νέα καρτέλα + + Άνοιγμα συνδέσμου σε ιδιωτική καρτέλα + + Άνοιγμα εικόνας σε νέα καρτέλα + + Λήψη συνδέσμου + + Κοινή χρήση συνδέσμου + + Κοινή χρήση εικόνας + + Αντιγραφή συνδέσμου + + Αντιγραφή τοποθεσίας εικόνας + + Αποθήκευση εικόνας + + Αντιγραφή εικόνας + + Αποθήκευση αρχείου στη συσκευή + + Άνοιξε νέα καρτέλα + + Άνοιξε νέα ιδιωτική καρτέλα + + Ο σύνδεσμος αντιγράφτηκε στο πρόχειρο + + Εναλλαγή + + Άνοιγμα συνδέσμου σε εξωτερική εφαρμογή + + Κοινή χρήση διεύθυνσης email + + Αντιγραφή διεύθυνσης email + + Η διεύθυνση email αντιγράφτηκε στο πρόχειρο + + Προσθήκη σε επαφή + + Αναζήτηση + + Ιδιωτική αναζήτηση + + Κοινή χρήση + + Email + + Κλήση + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-en-rCA/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-en-rCA/strings.xml new file mode 100644 index 0000000000..bdd087d978 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-en-rCA/strings.xml @@ -0,0 +1,53 @@ + + + + Open link in new tab + + Open link in private tab + + Open image in new tab + + Download link + + Share link + + Share image + + Copy link + + Copy image location + + Save image + + Copy image + + Save file to device + + New tab opened + + New private tab opened + + Link copied to clipboard + + Switch + + Open link in external app + + Share email address + + Copy email address + + Email address copied to clipboard + + Add to contact + + Search + + Private Search + + Share + + Email + + Call + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-en-rGB/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-en-rGB/strings.xml new file mode 100644 index 0000000000..bdd087d978 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-en-rGB/strings.xml @@ -0,0 +1,53 @@ + + + + Open link in new tab + + Open link in private tab + + Open image in new tab + + Download link + + Share link + + Share image + + Copy link + + Copy image location + + Save image + + Copy image + + Save file to device + + New tab opened + + New private tab opened + + Link copied to clipboard + + Switch + + Open link in external app + + Share email address + + Copy email address + + Email address copied to clipboard + + Add to contact + + Search + + Private Search + + Share + + Email + + Call + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-eo/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-eo/strings.xml new file mode 100644 index 0000000000..7a19b601d0 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-eo/strings.xml @@ -0,0 +1,53 @@ + + + + Malfermi ligilon en nova langeto + + Malfermi ligilon en privata langeto + + Malfermi bildon en nova langeto + + Elŝuta ligilo + + Dividi ligilon + + Dividi bildon + + Kopii ligilon + + Kopii adreson de bildo + + Konservi bildon + + Kopii bildon + + Konservi dosieron en la aparato + + Nova langeto malfermita + + Nova privata langeto malfermita + + Ligilo kopiita al la tondujo + + Ŝanĝi + + Malfermi ligilon en ekstera programo + + Dividi retpoŝtan adreson + + Kopii retpoŝtan adreson + + Retpoŝta adreso kopiita al tondujo + + Aldoni al kontakto + + Serĉi + + Privata serĉo + + Dividi + + Retpoŝto + + Voki + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rAR/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rAR/strings.xml new file mode 100644 index 0000000000..ff7a8b5fa1 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rAR/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir enlace en una pestaña nueva + + Abrir enlace en una pestaña privada + + Abrir la imagen en una pestaña nueva + + Descargar enlace + + Compartir enlace + + Compartir imagen + + Copiar enlace + + Copiar ubicación de la imagen + + Guardar imagen + + Copiar imagen + + Guardar el archivo en el dispositivo + + Se abrió una pestaña nueva + + Se abrió una nueva pestaña privada + + Enlace copiado al portapapeles + + Intercambiar + + Abrir enlace en aplicación externa + + Compartir dirección de correo electrónico + + Copiar dirección de correo electrónico + + Dirección de correo electrónico copiada al portapapeles + + Agregar a contactos + + Buscar + + Búsqueda privada + + Compartir + + Correo electrónico + + Llamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rCL/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rCL/strings.xml new file mode 100644 index 0000000000..be8d68c8a8 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rCL/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir enlace en una pestaña nueva + + Abrir enlace en una pestaña privada + + Abrir imagen en una pestaña nueva + + Descargar enlace + + Compartir enlace + + Compartir imagen + + Copiar enlace + + Copiar dirección de imagen + + Guardar imagen + + Copiar imagen + + Guardar archivo en el dispositivo + + Nueva pestaña abierta + + Nueva pestaña privada abierta + + Enlace copiado al portapapeles + + Cambiar + + Abrir enlace en una aplicación externa + + Compartir email + + Copiar email + + Dirección de email copiada al portapapeles + + Añadir a contacto + + Buscar + + Búsqueda privada + + Compartir + + Correo + + Llamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rES/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rES/strings.xml new file mode 100644 index 0000000000..5dafec4216 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rES/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir enlace en una pestaña nueva + + Abrir enlace en una pestaña privada + + Abrir imagen en una pestaña nueva + + Descargar enlace + + Compartir enlace + + Compartir imagen + + Copiar enlace + + Copiar ubicación de la imagen + + Guardar imagen + + Copiar imagen + + Guardar el archivo en el dispositivo + + Se abrió una pestaña nueva + + Se abrió una nueva pestaña privada + + Enlace copiado al portapapeles + + Cambiar + + Abrir enlace en aplicación externa + + Compartir la dirección de correo electrónico + + Copiar dirección de correo electrónico + + Dirección de correo electrónico copiada al portapapeles + + Añadir a los contactos + + Buscar + + Búsqueda privada + + Compartir + + Correo electrónico + + Llamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rMX/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rMX/strings.xml new file mode 100644 index 0000000000..a26ed46bf6 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es-rMX/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir enlace en una pestaña nueva + + Abrir enlace en una pestaña privada + + Abrir imagen en una pestaña nueva + + Descargar enlace + + Compartir enlace + + Compartir imagen + + Copiar enlace + + Copiar ubicación de la imagen + + Guardar imagen + + Copiar imagen + + Guardar el archivo en el dispositivo + + Nueva pestaña abierta + + Nueva pestaña privada abierta + + Enlace copiado al portapapeles + + Cambiar + + Abrir enlace en aplicación externa + + Compartir dirección de correo electrónico + + Copiar dirección de correo electrónico + + Dirección de correo copiada al portapeles + + Agregar al contacto + + Buscar + + Búsqueda privada + + Compartir + + Correo electrónico + + Llamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es/strings.xml new file mode 100644 index 0000000000..5dafec4216 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-es/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir enlace en una pestaña nueva + + Abrir enlace en una pestaña privada + + Abrir imagen en una pestaña nueva + + Descargar enlace + + Compartir enlace + + Compartir imagen + + Copiar enlace + + Copiar ubicación de la imagen + + Guardar imagen + + Copiar imagen + + Guardar el archivo en el dispositivo + + Se abrió una pestaña nueva + + Se abrió una nueva pestaña privada + + Enlace copiado al portapapeles + + Cambiar + + Abrir enlace en aplicación externa + + Compartir la dirección de correo electrónico + + Copiar dirección de correo electrónico + + Dirección de correo electrónico copiada al portapapeles + + Añadir a los contactos + + Buscar + + Búsqueda privada + + Compartir + + Correo electrónico + + Llamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-et/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-et/strings.xml new file mode 100644 index 0000000000..7f6c38aac1 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-et/strings.xml @@ -0,0 +1,53 @@ + + + + Ava link uuel kaardil + + Ava link uuel privaatsel kaardil + + Ava pilt uuel kaardil + + Laadi lingil olev sisu alla + + Jaga linki + + Jaga pilti + + Kopeeri link + + Kopeeri pildi asukoht + + Salvesta pilt + + Kopeeri pilt + + Salvesta fail seadmesse + + Avati uus kaart + + Avati uus privaatne kaart + + Link kopeeriti vahemällu + + Lülitu + + Ava link välises äpis + + Jaga e-posti aadressi + + Kopeeri e-posti aadress + + E-posti aadress kopeeriti vahemällu + + Lisa kontaktile + + Otsi + + Privaatne otsing + + Jaga + + Saada e-kiri + + Helista + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-eu/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-eu/strings.xml new file mode 100644 index 0000000000..34ded4679b --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-eu/strings.xml @@ -0,0 +1,53 @@ + + + + Ireki lotura fitxa berrian + + Ireki lotura fitxa pribatuan + + Ireki irudia fitxa berrian + + Deskargatu lotura + + Partekatu lotura + + Partekatu irudia + + Kopiatu lotura + + Kopiatu irudiaren helbidea + + Gorde irudia + + Kopiatu irudia + + Gorde fitxategia gailuan + + Fitxa berria ireki da + + Fitxa pribatu berria ireki da + + Lotura arbelean kopiatuta + + Aldatu + + Ireki lotura kanpoko aplikazioan + + Partekatu helbide elektronikoa + + Kopiatu helbide elektronikoa + + Helbide elektronikoa arbelean kopiatu da + + Gehitu kontaktura + + Bilatu + + Bilatu modu pribatuan + + Partekatu + + Helbide elektronikoa + + Deitu + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fa/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fa/strings.xml new file mode 100644 index 0000000000..99e12ffb48 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fa/strings.xml @@ -0,0 +1,53 @@ + + + + بازکردن پیوند در زبانه جدید + + باز کردن پیوند در زبانه خصوصی + + باز کردن تصویر در زبانه جدید + + بارگیری پیوند + + هم‌رسانی پیوند + + هم‌رسانی تصویر + + رونوشت از پیوند + + رونوشت از مکان تصویر + + ذخیره تصویر + + رونوشت از تصویر + + ذخیرهٔ پرونده در افزاره + + زبانهٔ جدید باز شد + + زبانهٔ خصوصی جدید باز شد + + پیوند به تخته‌گیره رونوشت شد + + تعویض + + گشودن پیوند در کاره‌ای دیگر + + هم‌رسانی نشانی رایانامه + + رونوشت از نشانی رایانامه + + نشانی رایانامه به تخته‌گیره رونوشت شد + + افزودن به مخاطبین + + جست‌وجو + + جست‌وجوی ناشناس + + هم‌رسانی + + رایانامه + + تماس + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ff/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ff/strings.xml new file mode 100644 index 0000000000..18fbe75b1b --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ff/strings.xml @@ -0,0 +1,41 @@ + + + + Uddit jokkol e tabbere hesere + + Uddit jokkol e tabbere suuriinde + + Uddit natal e tabbere hesere + + Aawto jokkol + + Lollin jokkol + + Natto jokkol + + Natto nokku natal + + Danndu natal + + Danndu fiilde e masiŋel + + Tabbere hesere udditaama + + Tabbere suturo hesere udditaama + + Jokkorde nattaama e ɗakkitorde + + Yah toon + + Uddit jokkol e jaaɓnirgal gonngal boowal + + Ñiiɓirde iimeel nattaama e ɗakkitorde + + Yiylo + + Njiilaw Suturo + + Lollin + + Noddu + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fi/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000000..648994b98b --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fi/strings.xml @@ -0,0 +1,53 @@ + + + + Avaa linkki uuteen välilehteen + + Avaa linkki uuteen yksityiseen välilehteen + + Avaa kuva uuteen välilehteen + + Lataa linkki + + Jaa linkki + + Jaa kuva + + Kopioi linkki + + Kopioi kuvan sijainti + + Tallenna kuva + + Kopioi kuva + + Tallenna tiedosto laitteelle + + Uusi välilehti avattu + + Uusi yksityinen välilehti avattu + + Linkki kopioitu leikepöydälle + + Vaihda + + Avaa linkki ulkoisessa sovelluksessa + + Jaa sähköpostiosoite + + Kopioi sähköpostiosoite + + Sähköpostiosoite kopioitu leikepöydälle + + Lisää yhteystietoon + + Haku + + Yksityinen haku + + Jaa + + Sähköposti + + Puhelu + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fr/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000000..6d41216d61 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fr/strings.xml @@ -0,0 +1,59 @@ + + + + Ouvrir le lien dans un nouvel onglet + + + Ouvrir le lien dans un nouvel onglet privé + + + Ouvrir l’image dans un nouvel onglet + + Télécharger la cible du lien + + Partager le lien + + + Partager l’image + + Copier le lien + + + Copier l’adresse de l’image + + Enregistrer l’image + + + Copier l’image + + Enregistrer le fichier sur l’appareil + + Nouvel onglet ouvert + + Nouvel onglet privé ouvert + + + Lien copié dans le presse-papiers + + Afficher + + Ouvrir le lien dans une application externe + + Partager l’adresse e-mail + + Copier l’adresse e-mail + + L’adresse e-mail a été copiée dans le presse-papiers + + Ajouter à un contact + + Rechercher + + Recherche privée + + Partager + + Envoyer un e-mail + + Appeler + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fur/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fur/strings.xml new file mode 100644 index 0000000000..72f3c44469 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fur/strings.xml @@ -0,0 +1,53 @@ + + + + Vierç link intune gnove schede + + Vierç link intune schede privade + + Vierç imagjin intune gnove schede + + Discjame link + + Condivît link + + Condivît imagjin + + Copie link + + Copie posizion imagjin + + Salve imagjin + + Copie imagjin + + Salve file sul dispositîf + + Vierte gnove schede + + Vierte gnove schede privade + + Link copiât intes notis + + Passe a + + Vierç link intune aplicazion esterne + + Condivît direzion e-mail + + Copie direzion e-mail + + Direzion e-mail copiade intes notis + + Zonte a un contat + + Cîr + + Ricercje privade + + Condivît + + Mande e-mail + + Clame + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fy-rNL/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fy-rNL/strings.xml new file mode 100644 index 0000000000..2f10e83864 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-fy-rNL/strings.xml @@ -0,0 +1,53 @@ + + + + Keppeling iepenje yn nij ljepblêd + + Keppeling iepenje yn priveeljepblêd + + Ofbylding iepenje yn nij ljepblêd + + Keppeling downloade + + Keppeling diele + + Ofbylding diele + + Keppeling kopiearje + + Ofbyldingslokaasje kopiearje + + Ofbylding bewarje + + Ofbylding kopiearje + + Bestân op apparaat bewarje + + Nij ljepblêd iepene + + Nij priveeljepblêd iepene + + Koppeling nei klamboerd kopiearre + + Wikselje + + Keppeling yn eksterne app iepenje + + E-mailadres diele + + E-mailadres kopiearje + + E-mailadres nei klamboerd kopiearre + + Oan kontakt tafoegje + + Sykje + + Privee sykje + + Diele + + E-maile + + Belje + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ga-rIE/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ga-rIE/strings.xml new file mode 100644 index 0000000000..e99740765f --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ga-rIE/strings.xml @@ -0,0 +1,26 @@ + + + + Oscail an nasc i gcluaisín nua + + Oscail an nasc i gcluaisín príobháideach + + Oscail an íomhá i gcluaisín nua + + Comhroinn an nasc + + Cóipeáil an nasc + + Cóipeáil suíomh na híomhá + + Sábháil an íomhá + + Osclaíodh cluaisín nua + + Osclaíodh cluaisín nua príobháideach + + Cóipeáladh an nasc go dtí an ghearrthaisce + + Malartaigh + Oscail an nasc in aip eile + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gd/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gd/strings.xml new file mode 100644 index 0000000000..50d1ca1ab8 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gd/strings.xml @@ -0,0 +1,53 @@ + + + + Fosgail an ceangal ann an taba ùr + + Fosgail an ceangal le taba prìobhaideach + + Fosgail an dealbh ann an taba ùr + + Luchdaich a-nuas an ceangal + + Co-roinn an ceangal + + Co-roinn an dealbh + + Dèan lethbhreac dhen cheangal + + Dèan lethbhreac de sheòladh an deilbh + + Sàbhail an dealbh + + Dèan lethbhreac dhen dealbh + + Sàbhail am faidhle air uidheam + + Chaidh taba ùr fhosgladh + + Chaidh taba prìobhaideach ùr fhosgladh + + Chaidh lethbhreac dhen cheangal a chur air an stòr-bhòrd + + Geàrr leum + + Fosgail an ceangal le aplacaid eile + + Co-roinn an seòladh puist-d + + Dèan lethbhreac de sheòladh a’ phuist-d + + Chaidh lethbhreac dhen t-seòladh phuist-d a chur air an stòr-bhòrd + + Cuir ris an neach-aithne + + Lorg + + Lorg prìobhaideach + + Co-roinn + + Post-d + + Cuir fòn + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gl/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gl/strings.xml new file mode 100644 index 0000000000..d5fed3e112 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gl/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir a ligazón nunha lapela nova + + Abrir a ligazón nunha lapela privada + + Abrir a ligazón nunha lapela nova + + Descargar ligazón + + Compartir ligazón + + Compartir imaxe + + Copiar ligazón + + Copiar a localización da imaxe + + Gardar imaxe + + Copiar a imaxe + + Gardar o ficheiro no dispositivo + + Abriuse unha lapela nova + + Abriuse unha lapela privada nova + + Copiouse a ligazón ao portapapeis + + Cambiar + + Abrir ligazón nunha aplicación externa + + Compartir correo electrónico + + Copiar correo electrónico + + Copiouse o enderezo de correo electrónico ao portapapeis + + Engadir a contacto + + Buscar + + Busca privada + + Compartir + + Correo electrónico + + Chamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gn/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gn/strings.xml new file mode 100644 index 0000000000..bcba32fe9e --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gn/strings.xml @@ -0,0 +1,53 @@ + + + + Embojuruja juajuha tendayke pyahúpe + + Embojuruja juajuha tendayke ñemíme + + Embojuruja ta’ãnga tendayke pyahúpe + + Juajuha mboguejyrã + + Emoherakuã juajuha + + Emoherakuã ta’ãnga + + Emonguatia juajuha + + Emonguatia ta’ãnga rendaite + + Eñongatu ta’ãnga + + Embokuatia ta’ãnga + + Eñongatu marandurenda mba’e’okápe + + Ijuruja tendayke pyahu + + Ijuruja tendayke pyahu ñemigua + + Juajuha monguatiapyre kuatiajokohápe + + Moambue + + Embojuruja juajuha tembiporu’i okayguápe + + Emoherakuã email + + Emonguatia email + + Ñanduti veve kundaharape ohasáva kuatiajokohápe + + Embojuaju terarenda + + Heka + + Ñemigua jeheka + + Moherakuã + + Ñanduti veve + + Henói + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gu-rIN/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gu-rIN/strings.xml new file mode 100644 index 0000000000..688f6d28b3 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-gu-rIN/strings.xml @@ -0,0 +1,45 @@ + + + + નવા ટેબમાં લિંક ખોલો + + ખાનગી ટેબમાં લિંક ખોલો + + નવા ટેબમાં છબી ખોલો + + લિંક ડાઉનલોડ કરો + + લિંક શેર કરો + + છબી શેર કરો + + લિંક કૉપિ કરો + + છબી સ્થાન કૉપિ કરો + + છબી સાચવો + + ઉપકરણ પર ફાઇલ સાચવો + + નવી ટૅબ ખૂલી ગઇ છે + + નવું ખાનગી ટેબ ખોલ્યું + + ક્લિપબોર્ડ પર લિંક કૉપિ કરી + + બદલો + + અન્ય એપ્લિકેશનમાં લિંક ખોલો + + સંપર્કમાં ઉમેરો + + શોધો + + ખાનગી શોધ + + શેર કરો + + ઈમેલ કરો + + કૉલ કરો + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hi-rIN/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hi-rIN/strings.xml new file mode 100644 index 0000000000..d7e2ee8db7 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hi-rIN/strings.xml @@ -0,0 +1,51 @@ + + + + लिंक को नए टैब में खोलें + + लिंक को निजी टैब में खोलें + + चित्र को नए टैब में खोलें + + लिंक डाउनलोड करें + + लिंक साझा करें + + चित्र साझा करें + + लिंक कॉपी करें + + चित्र पता कॉपी करें + + चित्र सहेजें + + डिवाइस पर फ़ाइल सहेजें + + नया टैब खुल गया + + नया निजी टैब खुल गया + + लिंक क्लिपबोर्ड में कॉपी हो गई + + बदलें + + लिंक को बाहरी ऐप में खोलें + + ईमेल पता साझा करें + + ईमेल पता कॉपी करें + + ईमेल पता क्लिपबोर्ड में कॉपी हो गया + + संपर्क में जोड़ें + + खोजें + + निजी खोज + + साझा करें + + ईमेल + + कॉल करें + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hil/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hil/strings.xml new file mode 100644 index 0000000000..4a8b863d23 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hil/strings.xml @@ -0,0 +1,9 @@ + + + + Pangitaon + + Email + + Tawagan + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hr/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hr/strings.xml new file mode 100644 index 0000000000..121a76a730 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hr/strings.xml @@ -0,0 +1,53 @@ + + + + Otvori poveznicu u novoj kartici + + Otvori poveznicu u privatnoj kartici + + Otvori sliku u novoj kartici + + Preuzmi poveznicu + + Dijeli poveznicu + + Podijeli sliku + + Kopiraj poveznicu + + Kopiraj lokaciju slike + + Spremi sliku + + Kopiraj sliku + + Spremi datoteku na uređaj + + Nova kartica otvorena + + Nova privatna kartica otvorena + + Poveznica kopirana u međuspremnik + + Prebaci + + Otvori poveznicu u vanjskoj aplikaciji + + Podijeli adresu e-pošte + + Kopiraj adresu e-pošte + + Adresa e-pošte kopirana je u međuspremnik + + Dodaj kontaktu + + Traži + + Privatna pretraga + + Dijeli + + E-pošta + + Poziv + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hsb/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hsb/strings.xml new file mode 100644 index 0000000000..a14df59663 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hsb/strings.xml @@ -0,0 +1,53 @@ + + + + Wotkaz w nowym rajtarku wočinić + + Wotkaz w priwatnym rajtarku wočinić + + Wobraz w nowym rajtarku wočinić + + Sćehnjenski wotkaz + + Wotkaz dźělić + + Wobraz dźělić + + Wotkaz kopěrować + + Wobrazowu adresu kopěrować + + Wobraz składować + + Wobraz kopěrować + + Dataju na graće składować + + Nowy rajtark je so wočinił + + Nowy priwatny rajtark je so wočinił + + Wotkaz je so do mjezyskłada kopěrował + + Přepinać + + Wotkaz w eksternym nałoženju wočinić + + E-mejlowu adresu dźělić + + E-mejlowu adresu kopěrować + + E-mejlowa adresa je so do mjezyskłada kopěrowała + + Kontaktej přidać + + Pytać + + Priwatne pytanje + + Dźělić + + E-mejl + + Wołać + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hu/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000000..135f895f5e --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hu/strings.xml @@ -0,0 +1,53 @@ + + + + Hivatkozás megnyitása új lapon + + Hivatkozás megnyitása privát lapon + + Kép megnyitása új lapon + + Hivatkozás letöltése + + Hivatkozás megosztása + + Kép megosztása + + Hivatkozás másolása + + Kép címének másolása + + Kép mentése + + Kép másolása + + Fájl mentése az eszközre + + Új lap nyílt meg + + Új privát lap nyílt meg + + Hivatkozás vágólapra másolva + + Kapcsolja át + + A hivatkozás megnyitása egy külső alkalmazásban + + E-mail cím megosztása + + E-mail cím másolása + + Az e-mail cím vágólapra másolva + + Hozzáadás a névjegyhez + + Keresés + + Privát keresés + + Megosztás + + E-mail + + Hívás + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hy-rAM/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hy-rAM/strings.xml new file mode 100644 index 0000000000..608338dc61 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-hy-rAM/strings.xml @@ -0,0 +1,53 @@ + + + + Բացել հղումը նոր ներդիրում + + Բացել հղումը Մասնավոր ներդիրում + + Բացել պատկերը նոր ներդիրում + + Ներբեռնել հղումը + + Տարածել հղումը + + Տարածել նկարը + + Պատճենել հղումը + + Պատճենել պատկերի հասցեն + + Պահպանել պատկերը + + Պատճենել պատկերը + + Պահպանել ֆայլը սարքում + + Նոր ներդիր է բացվել + + Նոր գաղտնի ներդիրը բացվեց + + Հղումը պատճենվել է սեղմատախտակին + + Անցնել + + Բացել հղումը արտաքին հավելվածում + + Տարածել էլ. հասցեն + + Պատճենել էլ. փոստը + + Էլ. փոստը պատճեմված է + + Ավելացնել կոնտակտներում + + Որոնում + + Մասնավոր որոնում + + Տարածել + + Էլ. փոստ + + Զանգ + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ia/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ia/strings.xml new file mode 100644 index 0000000000..4731db1b2f --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ia/strings.xml @@ -0,0 +1,53 @@ + + + + Aperir le ligamine in un nove scheda + + Aperir ligamine in scheda private + + Aperir imagine in nove scheda + + Discargar le ligamine + + Compartir le ligamine + + Compartir le imagine + + Copiar ligamine + + Copiar le ubication del imagine + + Salvar le imagine + + Copiar imagine + + Salvar file a in apparato + + Nove scheda aperite + + Nove scheda private aperite + + Ligamine copiate al area de transferentia + + Commutar + + Aperir le ligamine in un application externe + + Compartir le adresse email + + Copiar le adresse email + + Adresse email copiate al area de transferentia + + Adder al contactos + + Cercar + + Recerca private + + Compartir + + Email + + Appellar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-in/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-in/strings.xml new file mode 100644 index 0000000000..f2ce06941c --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-in/strings.xml @@ -0,0 +1,53 @@ + + + + Buka tautan di tab baru + + Buka tautan di tab pribadi + + Buka gambar di tab baru + + Tautan unduhan + + Bagikan tautan + + Bagikan gambar + + Salin tautan + + Salin lokasi gambar + + Simpan gambar + + Salin gambar + + Simpan berkas ke perangkat + + Tab baru dibuka + + Tab pribadi baru dibuka + + Tautan disalin ke papan klip + + Ganti + + Buka tautan di aplikasi eksternal + + Bagikan alamat surel + + Salin alamat surel + + Alamat surel disalin ke papan klip + + Tambah ke kontak + + Cari + + Pencarian Pribadi + + Bagikan + + Surel + + Panggil + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-is/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-is/strings.xml new file mode 100644 index 0000000000..b36cca0a26 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-is/strings.xml @@ -0,0 +1,53 @@ + + + + Opna tengil í nýjum flipa + + Opna tengil í huliðsflipa + + Opna mynd í nýjum flipa + + Niðurhalstengill + + Deila tengli + + Deila mynd + + Afrita tengil + + Afrita staðsetningu myndar + + Vista mynd + + Afrita mynd + + Vista skjal í tæki + + Nýr flipi opnaður + + Nýr huliðsflipi opnaður + + Tengill afritaður á klippispjald + + Skipta + + Opna tengil með ytra smáforriti + + Deila tölvupóstfangi + + Afrita tölvupóstfang + + Tölvupóstfang afritað á klippispjald + + Bæta við tengilið + + Leita + + Einkaleit + + Deila + + Tölvupóstur + + Hringja + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-it/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-it/strings.xml new file mode 100644 index 0000000000..8c9e243176 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-it/strings.xml @@ -0,0 +1,53 @@ + + + + Apri link in nuova scheda + + Apri in scheda anonima + + Apri immagine in nuova finestra + + Scarica link + + Condividi link + + Condividi immagine + + Copia link + + Copia indirizzo immagine + + Salva immagine + + Copia immagine + + Salva file sul dispositivo + + Aperta nuova scheda + + Aperta nuova scheda anonima + + Link copiato negli appunti + + Passa a + + Apri link in un’app esterna + + Condividi indirizzo email + + Copia indirizzo email + + Indirizzo email copiato negli appunti + + Aggiungi a un contatto + + Cerca + + Ricerca anonima + + Condividi + + Invia email + + Chiama + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-iw/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-iw/strings.xml new file mode 100644 index 0000000000..36705f2d42 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-iw/strings.xml @@ -0,0 +1,53 @@ + + + + פתיחת קישור בלשונית חדשה + + פתיחת קישור בלשונית פרטית + + פתיחת תמונה בלשונית חדשה + + הורדת קישור + + שיתוף קישור + + שיתוף תמונה + + העתקת קישור + + העתקת מיקום תמונה + + שמירת תמונה + + העתקת תמונה + + שמירת קובץ למכשיר + + לשונית חדשה נפתחה + + לשונית פרטית חדשה נפתחה + + הקישור הועתק ללוח + + מעבר + + פתיחת קישור ביישומון חיצוני + + שיתוף כתובת דוא״ל + + העתקת כתובת דוא״ל + + כתובת הדוא״ל הועתקה ללוח + + הוספת איש קשר + + חיפוש + + חיפוש פרטי + + שיתוף + + דוא״ל + + חיוג + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ja/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000000..e549b76d3b --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ja/strings.xml @@ -0,0 +1,62 @@ + + + + リンクを新しいタブで開く + + + リンクをプライベートタブで開く + + + 画像を新しいタブで開く + + + リンク先をダウンロード + + リンクを共有 + + + 画像を共有 + + リンクをコピー + + + 画像の URL をコピー + + + 画像を保存 + + + 画像をコピー + + ファイルを端末に保存 + + 新しいタブを開きました + + + プライベートタブを開きました + + + リンクをクリップボードにコピーしました + + 切り替え + + リンクを外部アプリで開く + + メールアドレスを共有 + + メールアドレスをコピー + + メールアドレスをクリップボードにコピーしました + + 連絡先に追加 + + 検索 + + プライベート検索 + + 共有 + + メール + + 通話 + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ka/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ka/strings.xml new file mode 100644 index 0000000000..c660f2a49a --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ka/strings.xml @@ -0,0 +1,53 @@ + + + + ბმულის გახსნა ახალ ჩანართში + + ბმულის პირად ჩანართში გახსნა + + სურათის ახალ ჩანართში გახსნა + + ბმულის ჩამოტვირთვა + + ბმულის გაზიარება + + სურათის გაზიარება + + ბმულის ასლი + + სურათის მისამართის ასლი + + სურათის შენახვა + + სურათის ასლი + + შენახვა მოწყობილობაში + + ახალი ჩანართი გაიხსნა + + ახალი პირადი ჩანართი გაიხსნა + + ბმულის ასლი აღებულია + + გადასვლა + + ბმულის გარეშე პროგრამით გახსნა + + ელფოსტის გაზიარება + + ელფოსტის მისამართის ასლი + + ელფოსტის ასლი აღებულია + + პირის დამატება + + ძიება + + პირადი ძიება + + გაზიარება + + ელფოსტა + + დარეკვა + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kaa/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kaa/strings.xml new file mode 100644 index 0000000000..e9a5035982 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kaa/strings.xml @@ -0,0 +1,53 @@ + + + + Siltemeni jańa bette ashıw + + Siltemeni jeke bette ashıw + + Súwretti taza bette ashıw + + Siltemeni júklep alıw + + Siltemeni bólisiw + + Súwretti bólisiw + + Siltemeni kóshirip alıw + + Súwrettıń siltemesin kóshirip alıw + + Súwretti saqlaw + + Súwretti kóshirip alıw + + Fayldı qurılmaǵa saqlaw + + Jańa bet ashıldı + + Jańa jeke bet ashıldı + + Silteme almasıw buferine kóshirip alındı + + Ótiw + + Siltemeni sırtqı baǵdarlamada ashıw + + Elektron pochta mánzilin bólisiw + + Elektron pochta mánzilin kóshirip alıw + + Elektron pochta mánzili almasıw buferine kóshirip alındı + + Kontaktke qosıw + + Izlew + + Jeke izlew + + Bólisiw + + Email + + Qońıraw qılıw + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kab/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kab/strings.xml new file mode 100644 index 0000000000..74cb77ae12 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kab/strings.xml @@ -0,0 +1,53 @@ + + + + Ldi aseɣwen deg iccer amaynut + + Ldi aseɣwen deg iccer uslig + + Ldi-t tugna deg yiccer amaynut + + Aseɣwen n usader + + Bḍu aseɣwen + + Bḍu tugna + + Nɣel aseɣwen + + Nɣel tansa n tugna + + Sekles tugna + + Nɣel tugna + + Sekles afaylu ɣer ibenk + + Iccer amaynut yeldi + + Iccer uslig amaynut yelldi + + Aseɣen yenɣel ɣef aus + + Nṭew + + Ldi aseɣwen deg usnas azɣaray + + Bḍu tansa imayl + + Nɣel tansa imayl + + Tansa imayl tettwanɣel ɣer afus + + Rnu ɣer unermis + + Nadi + + Anadi uslig + + Bḍu + + Imayl + + Siwel + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kk/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kk/strings.xml new file mode 100644 index 0000000000..5d52bb2ba6 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kk/strings.xml @@ -0,0 +1,53 @@ + + + + Сілтемені жаңа бетте ашу + + Сілтемені жекелік бетінде ашу + + Суретті жаңа бетте ашу + + Сілтемені жүктеп алу + + Сілтемемен бөлісу + + Суретпен бөлісу + + Сілтемені көшіріп алу + + Суреттің сілтемесін көшіру + + Суретті сақтау + + Суретті көшіру + + Файлды құрылғыға сақтау + + Жаңа бет ашылды + + Жаңа жекелік беті ашылды + + Сілтеме алмасу буферіне көшірілді + + Ауысу + + Сілтемені сыртқы қолданбада ашу + + Эл. пошта адресімен бөлісу + + Эл. пошта адресін көшіріп алу + + Эл. пошта адресі алмасу буферіне көшірілді + + Контакттарға қосу + + Іздеу + + Жеке іздеу + + Бөлісу + + Эл. пошта + + Қоңырау + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kmr/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kmr/strings.xml new file mode 100644 index 0000000000..61fc1a07b7 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kmr/strings.xml @@ -0,0 +1,53 @@ + + + + Girêdanê di hilpekîna nû de veke + + Girêdanê di hilpekîna veşartî de veke + + Wêneyê di hilpekîna nû de veke + + Girêdana daxistinê + + Girêdanê parve bike + + Wêneyê parve bike + + Girêdanê kopî bike + + Cîgeha wêneyê kopî bike + + Wêneyê tomar bike + + Wêneyî kopî bike + + Dosyeyê li cîhazê tomar bike + + Hilpekîna nû vebû + + Hilpekîna veşartî ya nû vebû + + Girêdan li panoyê hate kopîkirin + + Derbas bibe + + Girêdanê di sepaneke din de veke + + Navnîşana emaîlê parve bike + + Navnîşana emaîlê kopî bike + + Navnîşana emaîlê li panoyê hate kopîkirin + + Tevlî kesî bike + + Lê bigere + + Lêgerîna veşartî + + Parve bike + + Emaîl + + Bigere + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kn/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kn/strings.xml new file mode 100644 index 0000000000..d757feff46 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-kn/strings.xml @@ -0,0 +1,43 @@ + + + + ಹೊಸ ಟ್ಯಾಬ್‌ನಲ್ಲಿ ಕೊಂಡಿಯನ್ನು ತೆರೆ + + ಕೊಂಡಿಯನ್ನು ಖಾಸಗಿ ಹಾಳೆಯಲ್ಲಿ ತೆರೆ + + ಹೊಸ ಟ್ಯಾಬ್‌ನಲ್ಲಿ ಚಿತ್ರವನ್ನು ತೆರೆ + + ಡೌನ್‌ಲೋಡ್ ಕೊಂಡಿ + + ಕೊಂಡಿಯನ್ನು ಹಂಚಿಕೊಳ್ಳಿ + + ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಿ + + ಕೊಂಡಿ ನಕಲಿಸು‍ + + ಚಿತ್ರದ ತಾಣವನ್ನು ಕಾಪಿ ಮಾಡು + + ಚಿತ್ರವನ್ನು ಉಳಿಸು + + ಫೈಲ್ ಅನ್ನು ಸಾಧನಕ್ಕೆ ಉಳಿಸಿ + + ಹೊಸ ಹಾಳೆ ತೆರೆಯಲಾಗಿದೆ + + ಹೊಸ ಖಾಸಗಿ ಹಾಳೆ ತೆಗೆಯಲಾಗಿದೆ + + ಲಿಂಕ್ ಅನ್ನು ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸಲಾಗಿದೆ + + ಬದಲಾಯಿಸು + + ಬಾಹ್ಯ ಅಪ್ಲಿಕೇಶನ್‌ನಲ್ಲಿ ಲಿಂಕ್ ತೆರೆಯಿರಿ + + ಹುಡುಕು + + ಖಾಸಗಿ ಹುಡುಕಾಟ + + ಹಂಚು + + ಇಮೇಲ್ + + ಕರೆ + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ko/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000000..07f7eb291b --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ko/strings.xml @@ -0,0 +1,53 @@ + + + + 링크를 새 탭에서 열기 + + 사생활 보호 탭에 링크 열기 + + 이미지를 새 탭에서 열기 + + 다운로드 링크 + + 링크 공유 + + 이미지 공유 + + 링크 복사 + + 이미지 주소 복사 + + 이미지 저장 + + 이미지 복사 + + 파일을 기기에 저장 + + 새 탭 열림 + + 새 사생활 보호 탭 열림 + + 링크가 클립보드에 복사됨 + + 전환 + + 외부 앱에서 링크 열기 + + 이메일 주소 공유 + + 이메일 주소 복사 + + 이메일 주소가 클립보드에 복사됨 + + 연락처에 추가 + + 검색 + + 사생활 보호 검색 + + 공유 + + 이메일 + + 통화 + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lij/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lij/strings.xml new file mode 100644 index 0000000000..a511fa71a8 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lij/strings.xml @@ -0,0 +1,35 @@ + + + + Arvi link in atro feuggio + + Arvi link in feuggio privou + + Arvi inmagine in atro feuggio + + Scarega link + + Condividdi link + + Condividdi inmagine + + Còpia link + + Còpia indirisso inmagine + + Sarva inmagine + + Sarva schedaio into dispoxitivo + + Neuvo feuggio averto + + Neuvo feuggio privou averto + + Link copiou in sci aponti + + Passa a + + Arvi link in app esterna + + Condividdi + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lo/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lo/strings.xml new file mode 100644 index 0000000000..49067eabb5 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lo/strings.xml @@ -0,0 +1,53 @@ + + + + ເປີດລີ້ງໃນແທັບໃຫມ່ + + ເປີດລີ້ງໃນແທັບສ່ວນຕົວ + + ເປີດຮູບພາບໃນແທັບໃຫມ່ + + ລິ້ງດາວໂຫລດ + + ແບ່ງປັນລີ້ງ + + ແບ່ງປັນຮູບພາບ + + ສຳເນົາລີ້ງ + + ສຳເນົາທີ່ຢູ່ຮູບພາບ + + ບັນທຶກຮູບພາບ + + ສຳເນົາຮູບ + + ບັນທຶກເອກະສານເຂົ້າໃນອຸປະກອນ + + ເປີດແທັບໃຫມ່ແລ້ວ + + ເປີດແທັບສ່ວນຕົວໃຫມ່ແລ້ວ + + ສຳເນົາລີ້ງໄປໄວ້ໃນຄຣິບບອດແລ້ວ + + ສັບປ່ຽນ + + ເປີດລິ້ງນີ້ໃນ app ພາຍນອກ + + ແບ່ງປັນທີ່ຢູ່ອີເມລ + + ສຳເນົາທີ່ຢູ່ອີເມລ + + ທີ່ຢູ່ອີເມລໄດ້ຖືກສຳເນົາໄປໄວ້ໃນຄຣິບບອດແລ້ວ + + ເພີ່ມເຂົ້າໄປໃນລາຍຊື່ຕິດຕໍ່ + + ຄົ້ນຫາ + + ຄົ້ນຫາແບບສ່ວນຕົວ + + ແບ່ງປັນ + + ອີເມລ + + ໂທ + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lt/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lt/strings.xml new file mode 100644 index 0000000000..57f86d0d57 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-lt/strings.xml @@ -0,0 +1,51 @@ + + + + Atverti naujoje kortelėje + + Atverti privačiojoje kortelėje + + Atverti paveikslą naujoje kortelėje + + Atsiųsti saitą + + Dalintis saitu + + Dalintis paveikslu + + Kopijuoti saitą + + Kopijuoti paveikslo adresą + + Įrašyti paveikslą + + Įrašyti failą į įrenginį + + Atverta nauja kortelė + + Atverta nauja privačioji kortelė + + Saitas nukopijuotas į iškarpinę + + Pereiti + + Atverti saitą išorinėje programoje? + + Dalintis el. pašto adresu + + Kopijuoti el. pašto adresą + + El. pašto adresas nukopijuotas į iškarpinę + + Pridėti prie adresato + + Ieškoti + + Privačioji paieška + + Dalintis + + Siųsti el. laišką + + Skambinti + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-mix/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-mix/strings.xml new file mode 100644 index 0000000000..cf671dd295 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-mix/strings.xml @@ -0,0 +1,17 @@ + + + + Kuna ña kunu kuncheu nu inka xikua + + Kuna ña kunu kuncheu nu inka xikua se´e + + Kuna tutu ndatavana nu inka xikua + + Ndatava link + + Stucha + + Korreo + + Kana + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ml/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ml/strings.xml new file mode 100644 index 0000000000..51434f5290 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ml/strings.xml @@ -0,0 +1,35 @@ + + + + കണ്ണി പുതിയ റ്റാബില്‍ തുറക്കുക + + കണ്ണി സ്വകാര്യ ടാബിൽ തുറക്കുക + + ചിത്രം പുതിയ ടാബിൽ തുറക്കുക + + കണ്ണി ഡൗൺലോഡ് ചെയ്യുക + + കണ്ണി പങ്കിടുക + + ചിത്രം പങ്കിടുക + + കണ്ണി പകർത്തുക + + ചിത്രത്തിന്റെ സ്ഥാനം പകർത്തുക + + ചിത്രം സൂക്ഷിക്കുക + + ഉപകരണത്തിലേക്ക് ഫയൽ സൂക്ഷിക്കുക + + പുതിയ ടാബ് തുറന്നു + + പുതിയ സ്വകാര്യ ടാബ് തുറന്നു + + കണ്ണി ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തിയിരിക്കുന്നു + + മാറുക + + ബാഹ്യ ആപ്പിൽ കണ്ണി തുറക്കുക + + പങ്കിടുക + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-mr/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-mr/strings.xml new file mode 100644 index 0000000000..a944fb2483 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-mr/strings.xml @@ -0,0 +1,49 @@ + + + + दुवा नवीन टॅबमध्ये उघडा + + दुवा खाजगी टॅबमध्ये उघडा + + नवीन टॅबमध्ये प्रतिमा उघडा + + डाउनलोड दुवा + + दुवा शेअर करा + + प्रतिमा सामयिक करा + + दुव्याची प्रत बनवा + + प्रतिमा ठिकाणाची प्रत बनवा + + प्रतिमा साठवा + + डिव्हाइसमध्ये फाईल जतन करा + + नवीन टॅब उघडला + + नवीन खाजगी टॅब उघडला + + क्लिपबोर्डवर दुव्याची प्रत बनवली + + बदला + + बाहेरील अॅपमध्ये दुवा उघडा + + ईमेल पत्ता शेअर करा + + ईमेल पत्त्याची प्रत बनवा + + संपर्कांत समावेश करा + + शोधा + + खाजगी शोध + + शेअर करा + + ईमेल + + कॉल करा + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-my/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-my/strings.xml new file mode 100644 index 0000000000..297550261d --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-my/strings.xml @@ -0,0 +1,51 @@ + + + + လင့်ခ်ကို တပ်ဗ်အသစ်တွင် ဖွင့်ပါ + + လင်ခ့်ကို ကိုယ်ပိုင်သီးသန့်သုံးတပ်ဗ်တွင်ဖွင့်ပါ + + တပ်ဗ်အသစ်တွင် ပုံဖွင့်ပါ + + ဒေါင်းလုဒ်လင်ခ့် + + လင်ခ့်အားမျှဝေပါ + + ပုံ မျှဝေရန် + + လင့်ခ်ကူးပါ + + ပုံတည်နေရာကူးယူပါ + + ရုပ်ပုံ သိမ်းဆည်းပါ + + ဖိုင်ကိုသင့်ထဲကိုသိမ်းပါ + + တပ်ဗ်အသစ် ဖွင့်ထားသည် + + သီးသန့်သုံး တပ်ဗ်တစ်ခုဖွင့်ထားပြီး + + လင့်ခ်ကို ကလစ်ဘုတ်သို့ ကူးယူပြီး + + ပြောင်းပါ + + ပြင်ပအက်ပ်တွင်လင့်ခ်ဖွင့်ပါ + + အီးမေလ်းလိပ်စာ မျှဝေမည် + + အီးမေလ်းအား ကူးမည် + + ကလစ်ဘုတ်သို့ အီးမေလ်းကိုကူးထားပြီး + + အဆက်အသွယ်ထဲသို့ထည့်ပါ + + ရှာရန် + + သီးသန့် ရှာရန် + + မျှ​ဝေရန် + + အီးမေလ်း + + ခေါ်ရန် + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nb-rNO/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nb-rNO/strings.xml new file mode 100644 index 0000000000..ecd8377ef7 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nb-rNO/strings.xml @@ -0,0 +1,53 @@ + + + + Åpne lenke i ny fane + + Åpne lenke i privat fane + + Åpne bilde i ny fane + + Last ned lenke + + Del lenke + + Del bilde + + Kopier lenke + + Kopier bildeplassering + + Lagre bilde + + Kopier bilde + + Lagre fil på enheten + + Ny fane åpnet + + Ny privat fane åpnet + + Lenke kopiert til utklippstavlen + + Bytt + + Åpne lenken i ekstern app + + Del e-postadresse + + Kopier e-postadresse + + E-postadresse kopiert til utklippstavlen + + Legg til i kontakt + + Søk + + Privat søk + + Del + + E-post + + Ring + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ne-rNP/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ne-rNP/strings.xml new file mode 100644 index 0000000000..82faabe7b7 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ne-rNP/strings.xml @@ -0,0 +1,51 @@ + + + + लिङ्कलाई एउटा नयाँ ट्याबमा खोल्नुहोस् + + निजी ट्याबमा लिङ्क खोल्नुहोस् + + तस्वीरलाई नयाँ ट्याबमा खोल्नुहोस् + + डाउनलोड लिङ्क + + सेयर लिङ्क + + सेयर तस्वीर + + लिङ्कलाई कपि गर्नुहोस् + + तस्वीरको स्थान कपि गर्नुहोस् + + तस्वीर सेभ गर्नुहोस् + + उपकरणमा फाइल सेभ गर्नुहोस् + + नयाँ ट्याब खोलिएको छ + + नयाँ निजी ट्याब खोलिएको छ + + क्लिपबोर्डमा लिङ्क कपि गरिएको छ + + स्वीच + + बाहिरी एपमा लिङ्क खोल्नुहोस् + + इमेल ठेगाना सेयर गर्नुहोस् + + इमेल ठेगाना कपि गर्नुहोस् + + क्लिपबोर्डमा इमेल ठेगाना कपि गरियो + + सम्पर्कमा थप्नुहोस् + + खोज्नुहोस् + + निजी खोज गर्नुहोस् + + सेयर गर्नुहोस् + + इमेल + + कल गर्नुहोस् + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nl/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000000..1c8f33e11b --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nl/strings.xml @@ -0,0 +1,53 @@ + + + + Koppeling openen in nieuw tabblad + + Koppeling openen in privétabblad + + Afbeelding openen in nieuw tabblad + + Koppeling downloaden + + Koppeling delen + + Afbeelding delen + + Koppeling kopiëren + + Afbeeldingslocatie kopiëren + + Afbeelding opslaan + + Afbeelding kopiëren + + Bestand op apparaat opslaan + + Nieuw tabblad geopend + + Nieuw privétabblad geopend + + Koppeling naar klembord gekopieerd + + Wisselen + + Koppeling in externe app openen + + E-mailadres delen + + E-mailadres kopiëren + + E-mailadres naar klembord gekopieerd + + Aan contact toevoegen + + Zoeken + + Privé zoeken + + Delen + + E-mailen + + Bellen + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nn-rNO/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nn-rNO/strings.xml new file mode 100644 index 0000000000..0491aa129c --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-nn-rNO/strings.xml @@ -0,0 +1,53 @@ + + + + Opne lenke i ny fane + + Opne lenke i privat fane + + Opne bilde i ny fane + + Last ned lenke + + Del lenke + + Del bildet + + Kopier lenke + + Kopier bildeplassering + + Lagre bilde + + Kopier bilde + + Lagre fil på eininga + + Ny fane opna + + Ny privat fane opna + + Lenke kopiert til utklippstavla + + Byt + + Opne lenka i ekstern app + + Del e-postadresse + + Kopier e-postadresse + + E-postadresse kopiert til utklippstavla + + Legg til i kontakt + + Søk + + Privat søk + + Del + + E-post + + Ring + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-oc/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-oc/strings.xml new file mode 100644 index 0000000000..936b64acc5 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-oc/strings.xml @@ -0,0 +1,53 @@ + + + + Dobrir dins un onglet novèl + + Dobrir en navigacion privada + + Dobrir l’imatge dins un onglet novèl + + Telecargar lo ligam + + Partejar lo ligam + + Partejar l’imatge + + Copiar lo ligam + + Copiar l’adreça de l’imatge + + Enregistrar l’imatge + + Copiar l’imatge + + Enregistrar lo fichièr sul periferic + + Onglet novèl dobèrt + + Onglet novèl privat dobèrt + + Copiat dins lo quichapapièrs + + Bascular + + Dobrir lo ligam dins una aplicacion extèrna + + Partejar l’adreça electronica + + Copiar l’adreça electronica + + Adreça copiada al quichapapièrs + + Apondre als contactes + + Cercar + + Recèrca privada + + Partejar + + Adreça electronica + + Sonar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-or/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-or/strings.xml new file mode 100644 index 0000000000..794cf992e2 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-or/strings.xml @@ -0,0 +1,45 @@ + + + + ନୂଆ ଟ୍ୟାବରେ ଲିଙ୍କ ଖୋଲନ୍ତୁ + + ଲିଙ୍କକୁ ବ୍ୟକ୍ତିଗତ ଟ୍ୟାବରେ ଖୋଲନ୍ତୁ + + ନୂଆ ଟ୍ୟାବରେ ଛବି ଖୋଲନ୍ତୁ + + ଲିଙ୍କ ଡାଉନଲୋଡ କରନ୍ତୁ + + ଲିଙ୍କ ବିତରଣ କରନ୍ତୁ + + ପ୍ରତିଛବିକୁ ସହଭାଗ କରନ୍ତୁ + + ଲିଙ୍କ ନକଲ କରନ୍ତୁ + + ପ୍ରତିଛବି ଅବସ୍ଥିତି ନକଲ କରନ୍ତୁ + + ପ୍ରତିଛବି ସଂରକ୍ଷଣ କରନ୍ତୁ + + ଫାଇଲକୁ ଉପକରଣରେ ସଞ୍ଚୟ କରନ୍ତୁ + + ନୂଆ ଟ୍ୟାବ ଖୋଲିଛି + + ନୂଆ ବ୍ୟକ୍ତିଗତ ଟ୍ୟାବ ଖୋଲାଗଲା + + କ୍ଲିପବୋର୍ଡରେ ନକଲ କରାହୋଇଛି + + ପରିବର୍ତ୍ତନ କରନ୍ତୁ + + ଇମେଲ ଠିକଣାକୁ ସହଭାଗ କରନ୍ତୁ + + ଇମେଲ ଠିକଣାକୁ ନକଲ କରନ୍ତୁ + + ସମ୍ପର୍କ ତାଲିକାରେ ଯୋଗ କରନ୍ତୁ + + ଖୋଜନ୍ତୁ + + ବିତରଣ + + ଇମେଲ + + କଲ + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pa-rIN/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pa-rIN/strings.xml new file mode 100644 index 0000000000..ac1eaa6277 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pa-rIN/strings.xml @@ -0,0 +1,53 @@ + + + + ਨਵੀਂ ਟੈਬ ‘ਚ ਲਿੰਕ ਖੋਲ੍ਹੋ + + ਲਿੰਕ ਨਿੱਜੀ ਟੈਬ ‘ਚ ਖੋਲ੍ਹੋ + + ਚਿੱਤਰ ਨਵੀਂ ਟੈਬ ‘ਚ ਖੋਲ੍ਹੋ + + ਡਾਊਨਲੋਡ ਲਿੰਕ + + ਲਿੰਕ ਸਾਂਝਾ ਕਰੋ + + ਚਿੱਤਰ ਸਾਂਝਾ ਕਰੋ + + ਲਿੰਕ ਕਾਪੀ ਕਰੋ + + ਚਿੱਤਰ ਟਿਕਾਣਾ ਕਾਪੀ ਕਰੋ + + ਚਿੱਤਰ ਸੰਭਾਲੋ + + ਚਿੱਤਰ ਨੂੰ ਕਾਪੀ ਕਰੋ + + ਫਾਇਲ ਡਿਵਾਈਸ ਉੱਤੇ ਸੰਭਾਲੋ + + ਨਵੀਂ ਟੈਬ ਖੋਲ੍ਹੀ + + ਨਵੀਂ ਪ੍ਰਾਈਵੈਟ ਟੈਬ ਖੋਲ੍ਹੀ + + ਲਿੰਕ ਕਲਿੱਪਬੋਰਡ ਲਈ ਕਾਪੀ ਕੀਤਾ + + ਬਦਲੋ + + ਬਾਹਰੀ ਐਪ ਵਿੱਚ ਲਿੰਕ ਖੋਲ੍ਹੋ + + ਈਮੇਲ ਸਿਰਨਾਵੇ ਨੂੰ ਸਾਂਝਾ ਕਰੋ + + ਈਮੇਲ ਸਿਰਨਾਵੇ ਨੂੰ ਕਾਪੀ ਕਰੋ + + ਈਮੇਲ ਸਿਰਨਾਵਾਂ ਕਲਿੱਪਬੋਰਡ ਵਿੱਚ ਕਾਪੀ ਕੀਤਾ + + ਸੰਪਰਕ ਵਿੱਚ ਜੋੜੋ + + ਖੋਜੋ + + ਨਿੱਜੀ ਖੋਜ + + ਸਾਂਝਾ ਕਰੋ + + ਈਮੇਲ + + ਕਾਲ + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pa-rPK/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pa-rPK/strings.xml new file mode 100644 index 0000000000..f732b4ba98 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pa-rPK/strings.xml @@ -0,0 +1,53 @@ + + + + نویں ٹیب چ کھولھو + + نجی ٹیب چ کھولھو + + نویں ٹیب چ تصویر کھولھو + + ایس پتے توں ڈاؤں‌لوڈ کرو + + پتہ سانجھا کرو + + تصویر سانجھا کرو + + پتہ کاپی کرو + + تصویر دا پتہ کاپی کرو + + تصویر ڈاؤں‌لوڈ کرو + + تصویر کاپی کرو + + فائل ڈاؤں‌لوڈ کرو + + نویں ٹیب کھولھی گئی + + نجی ٹیب کھولھی گئی + + پتہ کاپی کیتا گیا + + ہورناں نوں جاؤ + + باہری ایپ چ پتے نوں جاؤ + + ای‌میل دا پتہ سانجھا کرو + + ای‌میل دا پتہ کاپی کرو + + ای‌میل دا پتہ کاپی کیتا گیا + + رابطے وچ شامل کرو + + کھوجو + + نجی کھوجو + + سانجھا کرو + + ای‌میل بھیجو + + فون کرو + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pl/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000000..637a77f4de --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pl/strings.xml @@ -0,0 +1,53 @@ + + + + Otwórz odnośnik w nowej karcie + + Otwórz odnośnik w prywatnej karcie + + Otwórz obraz w nowej karcie + + Pobierz odnośnik + + Udostępnij odnośnik + + Udostępnij obraz + + Kopiuj odnośnik + + Kopiuj adres obrazu + + Zapisz obraz + + Kopiuj obraz + + Zapisz plik na urządzeniu + + Otwarto nową kartę + + Otwarto nową kartę prywatną + + Skopiowano odnośnik do schowka + + Przejdź + + Otwórz odnośnik w zewnętrznej aplikacji + + Udostępnij adres e-mail + + Kopiuj adres e-mail + + Skopiowano adres e-mail do schowka + + Dodaj do kontaktu + + Wyszukaj + + Wyszukaj prywatnie + + Udostępnij + + Wyślij e-mail + + Zadzwoń + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pt-rBR/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..43d49e945c --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir link em nova aba + + Abrir em aba privativa + + Abrir imagem em nova aba + + Baixar link + + Compartilhar link + + Compartilhar imagem + + Copiar link + + Copiar endereço da imagem + + Salvar imagem + + Copiar imagem + + Salvar arquivo no dispositivo + + Nova aba aberta + + Nova aba privativa aberta + + Link copiado para área de transferência + + Mostrar + + Abrir link em app externo + + Compartilhar endereço de email + + Copiar endereço de email + + Endereço de email copiado para área de transferência + + Adicionar em um contato + + Pesquisar + + Pesquisa privativa + + Compartilhar + + Email + + Chamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pt-rPT/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000000..4f2d3fefde --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1,53 @@ + + + + Abrir ligação num novo separador + + Abrir ligação num separador privado + + Abrir imagem num novo separador + + Transferir ligação + + Partilhar ligação + + Partilhar imagem + + Copiar ligação + + Copiar endereço da imagem + + Guardar imagem + + Copiar imagem + + Guardar ficheiro no dispositivo + + Novo separador aberto + + Novo separador privado aberto + + Ligação copiada para a área de transferência + + Alternar + + Abrir ligação numa aplicação externa + + Partilhar endereço de e-mail + + Copiar endereço de e-mail + + Endereço de e-mail copiado para a área de transferência + + Adicionar ao contacto + + Procurar + + Pesquisa privada + + Partilhar + + E-mail + + Chamar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-rm/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-rm/strings.xml new file mode 100644 index 0000000000..d3eda1fce9 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-rm/strings.xml @@ -0,0 +1,53 @@ + + + + Avrir la colliaziun en in nov tab + + Avrir la colliaziun en in nov tab privat + + Avrir la grafica en in nov tab + + Telechargiar la colliaziun + + Cundivider la colliaziun + + Cundivider la grafica + + Copiar la colliaziun + + Copiar l\'adressa da la grafica + + Memorisar la grafica + + Copiar il maletg + + Memorisar la datoteca sin l\'apparat + + Avert in nov tab + + Avert in nov tab privat + + Copià la colliaziun en l\'archiv provisoric + + Midar + + Avrir la colliaziun en ina app externa + + Cundivider l\'adressa d\'e-mail + + Copiar l\'adressa d\'e-mail + + Copià l\'adressa d\'e-mail en l\'archiv provisoric + + Agiuntar ad in contact + + Tschertgar + + Tschertga privata + + Cundivider + + E-mail + + Telefonar + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ro/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ro/strings.xml new file mode 100644 index 0000000000..7ad904493d --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ro/strings.xml @@ -0,0 +1,51 @@ + + + + Deschide linkul într-o filă nouă + + Deschide linkul într-o filă privată + + Deschide imaginea într-o filă nouă + + Descarcă linkul + + Partajează linkul + + Partajează imaginea + + Copiază linkul + + Copiază locația imaginii + + Salvează imaginea + + Salvează fișierul pe dispozitiv + + S-a deschis o filă nouă + + Filă privată nouă deschisă + + Linkul a fost copiat în clipboard + + Comută + + Deschide linkul într-o aplicație externă + + Partajează adresa de e-mail + + Copiază adresa de e-mail + + Adresă de e-mail copiată în clipboard + + Adaugă la contact + + Căutare + + Căutare privată + + Partajează + + E-mail + + Sună + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ru/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000000..dcee952da3 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ru/strings.xml @@ -0,0 +1,53 @@ + + + + Открыть ссылку в новой вкладке + + Открыть в приватной вкладке + + Открыть изображение в новой вкладке + + Загрузить по ссылке + + Поделиться ссылкой + + Поделиться изображением + + Копировать ссылку + + Копировать ссылку на изображение + + Сохранить изображение + + Копировать изображение + + Сохранить файл на устройстве + + Открыта новая вкладка + + Открыта новая приватная вкладка + + Ссылка скопирована в буфер обмена + + Перейти + + Открыть ссылку в другом приложении + + Поделиться адресом эл. почты + + Копировать адрес эл. почты + + Адрес эл. почты скопирован в буфер обмена + + Добавить в контакты + + Поиск + + Приватный поиск + + Поделиться + + Эл. почта + + Позвонить + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sat/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sat/strings.xml new file mode 100644 index 0000000000..1e102cf7fb --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sat/strings.xml @@ -0,0 +1,53 @@ + + + + ᱱᱟᱶᱟ ᱴᱮᱵᱽ ᱨᱮ ᱞᱤᱝᱠ ᱡᱷᱤᱡᱽ ᱢᱮ + + ᱯᱨᱭᱣᱮᱴ ᱴᱮᱵᱽ ᱨᱮ ᱞᱤᱝᱠ ᱡᱷᱤᱡᱽ ᱢᱮ + + ᱱᱟᱶᱟ ᱴᱮᱵᱽ ᱨᱮ ᱪᱤᱛᱟᱹᱨ ᱡᱷᱤᱡᱽ ᱢᱮ + + ᱰᱟᱣᱱᱞᱚᱰ ᱞᱤᱝᱠ + + ᱞᱤᱝᱠ ᱦᱟᱹᱴᱤᱧ ᱢᱮ + + ᱪᱤᱛᱟᱹᱨ ᱦᱟᱹᱴᱤᱧ ᱢᱮ + + ᱞᱤᱝᱠ ᱱᱚᱠᱚᱞ ᱢᱮ + + ᱪᱤᱛᱟᱹᱨ ᱨᱮᱭᱟᱜ ᱡᱟᱭᱜᱟ ᱱᱚᱠᱚᱞ ᱢᱮ + + ᱪᱤᱛᱟᱹᱨ ᱥᱟᱺᱪᱟᱣ ᱢᱮ + + ᱪᱤᱛᱟᱹᱨ ᱱᱚᱠᱚᱞ ᱢᱮ + + ᱨᱮᱫ ᱥᱟᱫᱷᱚᱱ ᱨᱮ ᱥᱟᱺᱪᱟᱣ ᱢᱮ + + ᱱᱟᱶᱟ ᱴᱮᱵᱽ ᱠᱷᱩᱞᱟᱹᱭ ᱮᱱᱟ + + ᱱᱟᱶᱟ ᱱᱤᱡᱚᱨᱟᱜ ᱴᱮᱵᱽ ᱠᱷᱩᱞᱟᱹᱭ ᱮᱱᱟ + + ᱞᱤᱝᱠ ᱨᱮᱴᱚᱵᱼᱵᱚᱰ ᱨᱮ ᱱᱚᱠᱚᱞᱮᱱᱟ + + ᱵᱚᱫᱚᱞ ᱢᱮ + + ᱞᱤᱝᱠ ᱵᱟᱦᱨᱮ ᱮᱯ ᱨᱮ ᱡᱷᱤᱡᱽ ᱢᱮ + + ᱤᱼᱢᱮᱞ ᱴᱷᱤᱠᱬᱟᱹ ᱠᱚ ᱦᱟᱹᱴᱤᱧ ᱢᱮ + + ᱤᱼᱢᱮᱞ ᱴᱷᱤᱠᱬᱟᱹ ᱱᱚᱠᱚᱞ ᱢᱮ + + ᱤᱼᱢᱮᱞ ᱴᱷᱤᱠᱬᱟᱹ ᱨᱮᱴᱚᱵᱼᱵᱚᱰ ᱨᱮ ᱱᱚᱠᱚᱞᱮᱱᱟ + + ᱥᱚᱢᱯᱚᱨᱠ ᱨᱮ ᱥᱮᱞᱮᱫᱽ ᱢᱮ + + ᱥᱮᱸᱫᱽᱨᱟ + + ᱱᱤᱡᱮᱨᱟᱜ ᱥᱮᱸᱫᱽᱨᱟ + + ᱦᱟᱹᱴᱤᱧ + + ᱤᱼᱢᱮᱞ + + ᱠᱚᱞ + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sc/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sc/strings.xml new file mode 100644 index 0000000000..580e3a1d74 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sc/strings.xml @@ -0,0 +1,53 @@ + + + + Aberi su ligòngiu in un’ischeda noa + + Aberi su ligòngiu in un’ischeda privada noa + + Aberi s’immàgine in un’ischeda noa + + Iscàrriga su ligòngiu + + Cumpartzi su ligòngiu + + Cumpartzi s’immàgine + + Còpia su ligòngiu + + Còpia sa positzione de s’immàgine + + Sarva s’immàgine + + Còpia s’immàgine + + Sarva s’immàgine in su dispositivu + + Ischeda noa aberta + + Ischeda privada noa aberta + + Ligòngiu copiadu in punta de billete + + Bae + + Aberi su ligòngiu in s’aplicatzione esterna + + Cumpartzi s’indiritzu de posta eletrònica + + Còpia s’indiritzu + + Indiritzu copiadu in punta de billete + + Agiunghe a su cuntatu + + Chirca + + Chirca privada + + Cumpartzi + + Indiritzu de posta eletrònica + + Muti + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-si/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-si/strings.xml new file mode 100644 index 0000000000..3c2deaffb8 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-si/strings.xml @@ -0,0 +1,53 @@ + + + + නව පටිත්තක සබැඳිය අරින්න + + පෞද්. පටිත්තක සබැඳිය අරින්න + + නව පටිත්තකින් රූපය බලන්න + + බාගැනීමේ සබැඳිය + + සබැඳිය බෙදාගන්න + + රූපය බෙදාගන්න + + සබැඳියේ පිටපතක් + + රූපයේ ස්ථානයෙහි පිටපතක් + + රූපය සුරකින්න + + රූපයේ පිටපතක් + + උපාංගයට ගොනුව සුරකින්න + + නව පටිත්තක් විවෘතයි + + නව පෞද්. පටිත්තක් විවෘතයි + + සබැඳිය පසුරු පුවරුවට පිටපත් විය + + මාරු වන්න + + බාහිර යෙදුමකින් සබැඳිය අරින්න + + වි-තැපෑල බෙදාගන්න + + වි-තැපෑලෙහි පිටපතක් + + වි-තැපෑල පසුරු පුවරුවට පිටපත් විය + + සබඳතාවයට යොදන්න + + සොයන්න + + පෞද්. සෙවුම + + බෙදාගන්න + + වි-තැපෑල + + අමතන්න + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sk/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000000..3b20611d30 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sk/strings.xml @@ -0,0 +1,53 @@ + + + + Otvoriť odkaz na novej karte + + Otvoriť odkaz na súkromnej karte + + Otvoriť obrázok na novej karte + + Stiahnuť odkaz + + Zdieľať odkaz + + Zdieľať obrázok + + Kopírovať odkaz + + Kopírovať adresu obrázka + + Uložiť obrázok + + Kopírovať obrázok + + Uložiť súbor do zariadenia + + Bola otvorená nová karta + + Bola otvorená nová súkromná karta + + Odkaz bol skopírovaný do schránky + + Prepnúť + + Otvoriť odkaz v inej aplikácii + + Zdieľať e‑mailovú adresu + + Kopírovať e‑mailovú adresu + + E‑mailová adresa bola skopírovaná do schránky + + Pridať medzi kontakty + + Hľadať + + Súkromne vyhľadať + + Zdieľať + + Poslať e‑mail + + Zavolať + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-skr/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-skr/strings.xml new file mode 100644 index 0000000000..dd26c0311a --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-skr/strings.xml @@ -0,0 +1,53 @@ + + + + نویں ٹیب وچ لنک کھولو + + نجی ٹیب وچ لنک کھولو + + نویں ٹیب وچ تصویر کھولو + + لنک ڈاؤن لوڈ کرو + + لنک شیئر کرو + + تصویر شیئر کرو + + لنک نقل کرو + + تصویر مقام نقل کرو + + تصویر محفوظ کرو + + تصویر کاپی کرو + + فائل ڈیوائس وچ محفوظ کرو + + نواں ٹیب کھل ڳیا + + نواں نجی ٹیب کھُل ڳیا + + لنک کلپ بورڈ تے نقل تھی ڳیا + + سوئچ + + لنک ٻاہرلی ایپ وچ کھولو + + ای میل پتہ شیئر کرو + + ای میل پتہ نقل کرو + + ای میل پتہ کلپ بورڈ تے نقل تھی ڳیا + + رابطہ وچ شامل کرو + + ڳولو + + نجی ڳولݨ + + شیئر + + ای میل + + فون کرو + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sl/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sl/strings.xml new file mode 100644 index 0000000000..7eedd1d51f --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sl/strings.xml @@ -0,0 +1,53 @@ + + + + Odpri povezavo v novem zavihku + + Odpri povezavo v zasebnem zavihku + + Odpri sliko v novem zavihku + + Prenesi povezavo + + Deli povezavo + + Deli sliko + + Kopiraj povezavo + + Kopiraj mesto slike + + Shrani sliko + + Kopiraj sliko + + Shrani datoteko na napravo + + Odprt nov zavihek + + Odprt nov zaseben zavihek + + Povezava kopirana v odložišče + + Preklopi + + Odpri povezavo v zunanji aplikaciji + + Deli e-poštni naslov + + Kopiraj e-poštni naslov + + E-poštni naslov kopiran v odložišče + + Dodaj k stiku + + Išči + + Zasebno iskanje + + Deli + + E-pošta + + Pokliči + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sq/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sq/strings.xml new file mode 100644 index 0000000000..44c5483f05 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sq/strings.xml @@ -0,0 +1,53 @@ + + + + Hape lidhjen në skedë të re + + Hape lidhjen në skedë private + + Hape figurën në skedë të re + + Lidhje shkarkimi + + Ndani lidhje me të tjerët + + Ndani figurë me të tjerët + + Kopjoje lidhjen + + Kopjo vendndodhje figure + + Ruaje figurën + + Kopjo figurën + + Ruajeni kartelën te pajisje + + U hap skedë e re + + U hap skedë e re private + + Lidhja u kopjua në të papastër + + Kaloni në të + + Hape lidhjen në aplikacion të jashtëm + + Jepe adresën email + + Kopjoje adresën email + + Adresa email u kopjua në të papastër + + Shtoje te kontaktet + + Kërko + + Kërkim Privat + + Ndajeni me të tjerë + + Email + + Thirrje + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sr/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sr/strings.xml new file mode 100644 index 0000000000..1794929708 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sr/strings.xml @@ -0,0 +1,53 @@ + + + + Отвори везу у новом језичку + + Отвори везу у приватном језичку + + Отвори слику у новом језичку + + Преузми везу + + Подели везу + + Подели слику + + Копирај везу + + Копирај локацију слике + + Сачувај слику + + Копирај слику + + Сачувај датотеку на уређај + + Нови језичак је отворен + + Нови приватни језичак је отворен + + Веза је копирана у привремену меморију + + Пребаци + + Отвори везу у спољној апликацији + + Подели адресу е-поште + + Копирај адресу е-поште + + Адреса е-поште је копирана у привремену меморију + + Додај у контакте + + Претрага + + Приватна претрага + + Дели + + Е-пошта + + Позови + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-su/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-su/strings.xml new file mode 100644 index 0000000000..c60cc95393 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-su/strings.xml @@ -0,0 +1,53 @@ + + + + Buka tutumbu di tab anyar + + Buka dina tab nyamuni + + Buka gambar di tab anyar + + Undeur tutumbu + + Bagikeun tutumbu + + Bagikeun gambar + + Tiron tutumbu + + Tiron pernah gambar + + Teundeun gambar + + Tiron gambar + + Teundeun berkas ka piranti + + Tab anyar dibuka + + Tab nyamuni anyar dibuka + + Tutumbu ditiron kana papan klip + + Gilir + + Buka tutumbu di aplikasi luar + + Bagikeun alamat surélék + + Tiron alamat surélék + + Alamat surélék geus ditiron kana papan klip + + Tambahkeun ka kontak + + Sungsi + + Sungsi Nyamuni + + Bagikeun + + Surélék + + Gero + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sv-rSE/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sv-rSE/strings.xml new file mode 100644 index 0000000000..ce908a7552 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-sv-rSE/strings.xml @@ -0,0 +1,53 @@ + + + + Öppna länk i ny flik + + Öppna länk i privat flik + + Öppna bild i ny flik + + Hämta länk + + Dela länk + + Dela bild + + Kopiera länk + + Kopiera bildadress + + Spara bild + + Kopiera bild + + Spara fil till enhet + + Ny flik öppnad + + Ny privat flik öppnad + + Länk kopierad till urklipp + + Växla + + Öppna länk i extern app + + Dela e-postadress + + Kopiera e-postadress + + E-postadress kopierad till urklipp + + Lägg till kontakt + + Sök + + Privat sökning + + Dela + + E-post + + Ring + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ta/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ta/strings.xml new file mode 100644 index 0000000000..b6a998ad59 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ta/strings.xml @@ -0,0 +1,51 @@ + + + + இணைப்பைப் புதிய கீற்றில் திற + + இணைப்பைக் கமுக்கக் கீற்றில் திற + + படத்தைப் புதிய கீற்றில் திற + + இணைப்பைத் பதிவிறக்கு + + தொடுப்பைப் பகிர் + + படத்தைப் பகிர் + + இணைப்பை நகலெடுக்கவும் + + பட இருப்பிடத்தை நகலெடுக்கவும் + + படத்தைச் சேமி + + கோப்பைக் கருவியில் சேமி + + புதிய கீற்று திறந்தது + + புதிய கமுக்கக் கீற்று திறந்தது + + இணைப்பு ஒட்டுப்பலகைக்கு நகலெடுக்கப்பட்டது + + தாவு + + இணைப்பை வேறு செயலியில் திற + + மின்னஞ்சல் முகவரியைப் பகிரவும் + + மின்னஞ்சல் முகவரியை நகலெடு + + மின்னஞ்சல் முகவரி ஒட்டுப்பலகைக்கு நகலெடுக்கப்பட்டது + + தொடர்பில் சேர் + + தேடு: + + கமுக்கத் தேடல் + + பகிர் + + மின்னஞ்சல் + + அழை + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-te/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-te/strings.xml new file mode 100644 index 0000000000..92564bd098 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-te/strings.xml @@ -0,0 +1,51 @@ + + + + లంకెను కొత్త ట్యాబులో తెరువు + + లంకెను అంతరంగిక ట్యాబులో తెరువు + + బొమ్మను కొత్త ట్యాబులో తెరువు + + లంకెను దించుకో + + లంకెను పంచుకోండి + + బొమ్మని పంచుకోండి + + లంకెను కాపీచేయి + + బొమ్మ స్థానాన్ని కాపీచేయి + + బొమ్మను భద్రపరుచు + + ఫైలును పరికరంలో భద్రపరచు + + కొత్త ట్యాబు తెరుచుకుంది + + కొత్త అంతరంగిక ట్యాబు తెరుచుకుంది + + లంకె క్లిప్‌బోర్డుకి కాపీ అయ్యింది + + మారు + + లంకెని బయటి అనువర్తనంలో తెరువు + + ఈమెయిలు చిరునామాను పంచుకో + + ఈమెయిలు చిరునామాను కాపీచేయి + + ఈమెయిలు చిరునామా క్లిప్‌బోర్డుకి కాపీ అయ్యింది + + పరిచయాలకు చేర్చు + + వెతకండి + + అంతరంగికంగా వెతకండి + + పంచుకోండి + + ఈమెయిలు + + ఫోనుచెయ్యండి + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tg/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tg/strings.xml new file mode 100644 index 0000000000..3ab9e7d1e4 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tg/strings.xml @@ -0,0 +1,53 @@ + + + + Кушодани пайванд дар варақаи нав + + Кушодани пайванд дар варақаи хусусӣ + + Кушодани тасвир дар варақаи нав + + Пайванди боргирӣ + + Мубодила кардани пайванд + + Мубодила кардани тасвир + + Нусха бардоштани пайванд + + Нусха бардоштани ҷойгиршавии тасвир + + Нигоҳ доштани тасвир + + Нусха бардоштани тасвир + + Нигоҳ доштани файл дар дастгоҳ + + Варақаи нав кушода шуд + + Варақаи хусусии нав кушода шуд + + Пайванд ба ҳофизаи муваққатӣ нусха бардошта шуд + + Гузариш + + Кушодани пайванд дар барномаи берунӣ + + Мубодила кардани нишонии почтаи электронӣ + + Нусха бардоштани нишонии почтаи электронӣ + + Нишонии почтаи электронӣ ба ҳофизаи муваққатӣ нусха бардошта шуд + + Илова кардан ба тамос + + Ҷустуҷӯ + + Ҷустуҷӯи хусусӣ + + Мубодила кардан + + Фиристодани паёми электронӣ + + Занг задан + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-th/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..f3b91aec36 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-th/strings.xml @@ -0,0 +1,53 @@ + + + + เปิดลิงก์ในแท็บใหม่ + + เปิดลิงก์ในแท็บส่วนตัว + + เปิดภาพในแท็บใหม่ + + ดาวน์โหลดลิงก์ + + แบ่งปันลิงก์ + + แบ่งปันภาพ + + คัดลอกลิงก์ + + คัดลอกตำแหน่งที่ตั้งภาพ + + บันทึกภาพ + + คัดลอกภาพ + + บันทึกไฟล์ไปยังอุปกรณ์ + + เปิดแท็บใหม่แล้ว + + เปิดแท็บส่วนตัวใหม่แล้ว + + คัดลอกลิงก์ไปยังคลิปบอร์ดแล้ว + + สลับ + + เปิดลิงก์ในแอปภายนอก + + แบ่งปันที่อยู่อีเมล + + คัดลอกที่อยู่อีเมล + + คัดลอกที่อยู่อีเมลไปยังคลิปบอร์ดแล้ว + + เพิ่มไปยังผู้ติดต่อ + + ค้นหา + + การค้นหาแบบส่วนตัว + + แบ่งปัน + + อีเมล + + โทร + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tl/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tl/strings.xml new file mode 100644 index 0000000000..32f0ca2003 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tl/strings.xml @@ -0,0 +1,51 @@ + + + + Buksan ang link sa panibagong tab + + Buksan ang link sa pribadong tab + + Buksan ang larawan sa bagong tab + + i-Download ang link + + Ibahagi ang link + + Ibahagi ang larawan + + Kopyahin ang link + + Koyahin ang lokasyon ng larawan + + I-save ang imahe + + i-Save ang file sa device + + Nagbukas ng bagong tab + + Nagbukas ng bagong pribadong tab + + Nakopya na ang teksto sa clipboard + + Lumipat + + Buksan ang link sa hiwalay na app + + Ibahagi ang mga email + + Kopyahin ang email address + + Nakopya na ang email address sa clipboard + + Idagdag sa contact + + Hanapin + + Pribadong Paghanap + + Ibahagi + + Mag-email + + Tawagan + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tr/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000000..7ec5f13dc7 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tr/strings.xml @@ -0,0 +1,53 @@ + + + + Bağlantıyı yeni sekmede aç + + Bağlantıyı gizli sekmede aç + + Resmi yeni sekmede aç + + Bağlantıyı indir + + Bağlantıyı paylaş + + Resmi paylaş + + Bağlantıyı kopyala + + Resim konumunu kopyala + + Resmi kaydet + + Resmi kopyala + + Dosyayı cihaza kaydet + + Yeni sekme açıldı + + Yeni gizli sekme açıldı + + Bağlantı panoya kopyalandı + + Buna geç + + Bağlantıyı başka uygulamada aç + + E-posta adresini paylaş + + E-posta adresini kopyala + + E-posta adresi panoya kopyalandı + + Kişiye ekle + + Ara + + Gizli arama + + Paylaş + + E-posta + + Çağrı yap + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-trs/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-trs/strings.xml new file mode 100644 index 0000000000..4cdfe223d1 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-trs/strings.xml @@ -0,0 +1,53 @@ + + + + Nā\'nïn lînk riña a\'ngô rakïj ñanj nakàa + + Nā\'nïn lînk riña \'ngō rakïj ñanj huìi + + Nā\'nïn ñadu\'ua riña a\'ngô rakïj ñanj nakàa + + Lînk riñā nadunïnjt + + Dūyinga\' lînk + + Dūyinga\' ñadu\'ua + + Gūxūn nī nāchrūnt a\'ngô hiūj u lînk + + Gūxūn nī nāchrūnt a\'ngô hiūj u riña \'na\' ñadu\'ua + + Nā\'nïnj sà\' ñadu\'ua + + Gīda’a ñanj dū’hua + + Nā\'nïnj sà\' riña si ārchibô aga\' nan + + Ngà nayî\'nïnj a\'ngô rakïj ñanj nākàa + + Ngà nayî\'nïnj a\'ngô rakïj ñanj nākà huìi + + Ngà nañû enlace riña portapapel + + Nādūnā + + Nā\'nïn lînk riña a\'ngô aplikasiûn + + Dūyinga’ si direksiun korreo + + Gūxun\' dirección korreo da\' nāchrūn\' a\'ngô hiūj u + + Ganatûj si direksiun korreo riña portapapel + + Gūnutò\’ man riña kontakto + + Nānà\'uì\' + + Nānà\'uì\' huì + + Dūyingô\' + + Korrêo + + Gā\’mīn + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tt/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tt/strings.xml new file mode 100644 index 0000000000..10bae29cfd --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tt/strings.xml @@ -0,0 +1,53 @@ + + + + Сылтаманы яңа биттә ачу + + Сылтаманы xосусый табта ачу + + Рәсемне яңа табта ачу + + Сылтаманы йөкләп алу + + Сылтаманы уртаклашу + + Рәсемне уртаклашу + + Сылтаманы күчереп алу + + Рәсемнең сылтамасын күчереп алу + + Рәсемне саклау + + Рәсемне копияләү + + Файлны җиһазга саклау + + Яңа таб ачылды + + Яңа хосусый таб ачылды + + Сылтама алмашу буферына күчермәләнде + + Күчү + + Сылтаманы тышкы кушымтада ачу + + Эл. почта адресын уртаклашу + + Эл. почта адресын күчереп алу + + Эл. почта адресы алмашу буферына күчермәләнде + + Контактларга өстәү + + Эзләү + + Хосусый эзләү + + Уртаклашу + + Эл. почта + + Шалтырату + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tzm/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tzm/strings.xml new file mode 100644 index 0000000000..4e55701fc4 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-tzm/strings.xml @@ -0,0 +1,41 @@ + + + + Rẓem asɣen g useksel amaynu + + Rẓem asɣen g useksel uslig + + Rẓem tawlaft g useksel amaynu + + Agem asɣen + + Bḍu asɣen + + Bḍu tawlaft + + Nɣel asɣun + + Agem tawlaft + + Agem afaylu ɣer wallal + + irzem useksel amaynu + + irzem useksel uslig amaynu + + Bḍu tansa imayl + + Nɣel tansa imayl + + Rnu ɣer wassaɣ + + Rzu + + Arezzu Uslig + + Bḍu + + Imayl + + Ɣer + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ug/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ug/strings.xml new file mode 100644 index 0000000000..b2eab24fa2 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ug/strings.xml @@ -0,0 +1,53 @@ + + + + ئۇلانمىنى يېڭى بەتكۈچتە ئېچىش + + ئۇلانمىنى شەخسىي بەتكۈچتە ئاچ + + رەسىمنى يېڭى بەتكۈچتە ئېچىش + + چۈشۈرۈش ئۇلانمىسى + + ئۇلانمىنى ھەمبەھىرلەش + + رەسىمنى ھەمبەھىرلەش + + ئۇلانمىنى كۆچۈرۈش + + سۈرەت ئورنىنى كۆچۈر + + رەسىمنى ساقلاش + + سۈرەت كۆچۈر + + ھۆججەتنى ئۈسكۈنىگە ساقلا + + يېڭى بەتكۈچ ئېچىلدى + + يېڭى شەخسىي بەتكۈچ ئېچىلدى + + ئۇلانما چاپلاش تاختىسىغا كۆچۈرۈلدى + + ئاتلاش + + ئۇلانمىنى سىرتقى ئەپتە ئېچىش + + ئېلخەت ئادرېسىنى ھەمبەھىرلەش + + ئېلخەت ئادرېسىنى كۆچۈرۈش + + ئېلخەت ئادرېسى چاپلاش تاختىسىغا كۆچۈرۈلدى + + ئالاقەداشقا قوش + + ئىزدەش + + شەخسىي ئىزدە + + ھەمبەھىرلەش + + ئېلخەت + + چاقىرىش + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-uk/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000000..fbf3fb6a47 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-uk/strings.xml @@ -0,0 +1,53 @@ + + + + Відкрити посилання в новій вкладці + + Відкрити посилання у приватній вкладці + + Відкрити зображення в новій вкладці + + Завантажити посилання + + Поділитись посиланням + + Поділитися зображенням + + Копіювати посилання + + Копіювати адресу зображення + + Зберегти зображення + + Копіювати зображення + + Зберегти файл на пристрій + + Відкрито нову вкладку + + Відкрита нова приватна вкладка + + Посилання скопійовано до буфера + + Перемкнути + + Відкрити посилання у зовнішній програмі + + Поділитись адресою е-пошти + + Копіювати адресу е-пошти + + Адресу е-пошти скопійовано до буферу обміну + + Додати до контакту + + Шукати + + Приватний пошук + + Поділитися + + Ел. поштою + + Виклик + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ur/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ur/strings.xml new file mode 100644 index 0000000000..dfc8c1094d --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-ur/strings.xml @@ -0,0 +1,51 @@ + + + + ربط نئی ٹیب میں کھولیں + + ربط نجی ٹیب میں کھولیں + + تصویر نئی ٹیب میں کھولیں + + ڈاؤن لوڈ ربط + + ربط شیئر کریں + + تصویر شیئر کریں + + ربط نقل کریں + + نقش محل وقوع نقل کریں + + نقش محفوظ کریں + + فائل کو آلہ میں محفوظ کریں + + نیا ٹیب کھلا + + نیا نجی ٹیب کھل گیا + + لنک کاپی کلپ بورڈ میں کیا گیا + + سوئچ + + لنک کو باهری ایپ میں کھولیں + + ای میل پتہ شیئر کریں + + ای میل پتہ نقل کریں + + ای میل پتہ کلپ بورڈ میں نقل کیا گیا + + رابطہ افراد میں ڈالیں + + تلاش کریں + + نجی تلاش + + شیئر + + ای میل + + کال کریں + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-uz/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-uz/strings.xml new file mode 100644 index 0000000000..223715070f --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-uz/strings.xml @@ -0,0 +1,51 @@ + + + + Havolani yangi varaqda ochish + + Havolani maxfiy varaqda ochish + + Rasmni yangi varaqda ochish + + Havola orqali yuklab olish + + Havolani ulashish + + Rasmni ulashish + + Havoladan nusxa olish + + Rasm manzilidan nusxa olish + + Rasmni saqlash + + Faylni qurilmaga saqlash + + Yangi varaq ochildi + + Yangi maxfiy varaq ochildi + + Havoladan klipboardga nusxa olindi + + Oʻtish + + Havolani tashqi ilovada ochish + + Email manzillarini boʻlishish + + Email manzilidan nusxa olish + + Email manzili vaqtinchalik xotiraga nusxalandi + + Kontaktga qoʻshish + + Izlash + + Maxfiy qidiruv + + Ulashish + + Email + + Qoʻngʻiroq qilish + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-vec/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-vec/strings.xml new file mode 100644 index 0000000000..9a2f0ab546 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-vec/strings.xml @@ -0,0 +1,49 @@ + + + + Vèrxi link en na nova scheda + + Vèrxi en na scheda anonima + + Vèrxi imagine en na nova fenèstra + + Scarega link + + Condividi link + + Condividi imaxene + + Còpia link + + Còpia indiriso imagine + + Salva imagine + + Còpia imaxene + + Salva el file so el dispoxitivo + + Vèrxi nova scheda + + Vèrta nova scheda anonima + + Link copià ne i scarabòci + + Pasa a + + Vèrxi en na app esterna + + Condividi indiriso email + + Còpia indiriso email + + Indiriso email copià intel i scaraboci + + Xonta a on contato + + Serca + + Reserca anonema + + Condividi + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-vi/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000000..68d8102afd --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-vi/strings.xml @@ -0,0 +1,53 @@ + + + + Mở liên kết trong thẻ mới + + Mở liên kết trong thẻ riêng tư mới + + Mở ảnh trong thẻ mới + + Tải xuống liên kết + + Chia sẻ liên kết + + Chia sẻ ảnh + + Sao chép liên kết + + Sao chép liên kết ảnh + + Lưu ảnh + + Sao chép ảnh + + Lưu tập tin vào thiết bị + + Thẻ mới đã mở + + Thẻ riêng tư mới đã mở + + Đã sao chép liên kết vào bộ nhớ tạm + + Chuyển + + Mở liên kết đến ứng dụng bên ngoài + + Chia sẻ địa chỉ email + + Sao chép địa chỉ email + + Đã sao chép địa chỉ email vào bộ nhớ tạm + + Thêm vào liên lạc + + Tìm kiếm + + Tìm kiếm riêng tư + + Chia sẻ + + E-mail + + Gọi + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-yo/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-yo/strings.xml new file mode 100644 index 0000000000..acc9af30f5 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-yo/strings.xml @@ -0,0 +1,51 @@ + + + + Ṣí líǹkì nínú táàbù tuntun + + Ṣí líǹkì nínú táàbù ìkọ̀kọ̀ + + Ṣí àwòrán nínú táàbù tuntun + + Ṣe ìgbàsílẹ̀ líǹkì + + Pín líǹkì + + Pín àwòrán + + Ṣe àdàkọ líǹkì + + Ṣe ìdàkọ ipò àwòrán + + Fi àwòrán pamọ́ + + Fi fáìlì pamọ́ sórí ẹ̀rọ + + Táàbù tuntún wà ní ṣíṣí + + Táàbù tuntun oníkọ̀kọ̀ wà ní ṣíṣí + + Líǹkì ti wà ní ìdàkọ sórí àtẹ-fọ́nrán + + Ṣe àyípadà + + Ṣí líǹkì nínú áàpù ìta + + Pín àdírẹ́sì ímeèlì + + Ṣe àdàkọ àdírẹ́sì ímeèlì + + Àdírẹ́sì ímeèlì ti wà ní àdàkọ sórí àtẹ-fọ́nrán + + Fikún àwọn olùbásọ̀rọ̀ + + Ṣe àwárí + + Àwárí ìkọ̀kọ̀ + + Pín + + Ímeèlì + + Ìpè + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-zh-rCN/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..7b2cc2b8ac --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,62 @@ + + + + 新建标签页打开链接 + + + 新建隐私标签页打开链接 + + + 新建标签页打开图像 + + + 下载链接 + + 分享链接 + + + 分享图像 + + 复制链接 + + + 复制图像地址 + + + 保存图像 + + + 复制图像 + + 将文件保存到设备中 + + 已打开新标签页 + + + 已新建隐私标签页 + + + 链接已复制到剪贴板 + + 切换 + + 在外部应用中打开链接 + + 分享电子邮件地址 + + 复制电子邮件地址 + + 电子邮件地址已复制到剪贴板 + + 添加到联系人 + + 搜索 + + 隐私搜索 + + 分享 + + 电邮 + + 呼叫 + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-zh-rTW/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..f942f7b9e6 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,62 @@ + + + + 用新分頁開啟鏈結 + + + 用隱私分頁開啟鏈結 + + + 用新分頁開啟圖片 + + + 下載鏈結 + + 分享鏈結 + + + 分享圖片 + + 複製鏈結 + + + 複製圖片網址 + + + 儲存圖片 + + + 複製圖片 + + 將檔案儲存到裝置上 + + 已開啟新分頁 + + + 已開啟新隱私分頁 + + + 已將鏈結複製至剪貼簿 + + 切換 + + 用外部應用程式開啟鏈結 + + 分享電子郵件地址 + + 複製電子郵件地址 + + 已將電子郵件地址複製至剪貼簿 + + 新增為聯絡人 + + 搜尋 + + 隱私搜尋 + + 分享 + + 發送郵件 + + 撥號 + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/colors.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/colors.xml new file mode 100644 index 0000000000..c82374b18e --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + + #1D1133 + #BFBFC9 + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/strings.xml new file mode 100644 index 0000000000..32c120724f --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/strings.xml @@ -0,0 +1,56 @@ + + + + + Open link in new tab + + Open link in private tab + + Open image in new tab + + Download link + + Share link + + Share image + + Copy link + + Copy image location + + Save image + + Copy image + + Save file to device + + New tab opened + + New private tab opened + + Link copied to clipboard + + Switch + + Open link in external app + + Share email address + + Copy email address + + Email address copied to clipboard + + Add to contact + + Search + + Private Search + + Share + + Email + + Call + \ No newline at end of file diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/style.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/style.xml new file mode 100644 index 0000000000..e89670a3fa --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/style.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt new file mode 100644 index 0000000000..867966194c --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt @@ -0,0 +1,2063 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import android.content.ClipboardManager +import android.content.Context +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.MainScope +import mozilla.components.browser.state.engine.EngineMiddleware +import mozilla.components.browser.state.selector.selectedTab +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.ContentState +import mozilla.components.browser.state.state.EngineState +import mozilla.components.browser.state.state.SessionState +import mozilla.components.browser.state.state.TabSessionState +import mozilla.components.browser.state.state.content.ShareInternetResourceState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.HitResult +import mozilla.components.feature.app.links.AppLinkRedirect +import mozilla.components.feature.app.links.AppLinksUseCases +import mozilla.components.feature.tabs.TabsUseCases +import mozilla.components.support.test.any +import mozilla.components.support.test.argumentCaptor +import mozilla.components.support.test.eq +import mozilla.components.support.test.libstate.ext.waitUntilIdle +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import mozilla.components.ui.widgets.SnackbarDelegate +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.Mockito +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@RunWith(AndroidJUnit4::class) +class ContextMenuCandidateTest { + + private lateinit var snackbarDelegate: TestSnackbarDelegate + + @Before + fun setUp() { + snackbarDelegate = TestSnackbarDelegate() + } + + @Test + fun `Default candidates sanity check`() { + val candidates = ContextMenuCandidate.defaultCandidates(testContext, mock(), mock(), mock()) + // Just a sanity check: When changing the list of default candidates be aware that this will affect all + // consumers of this component using the default list. + assertEquals(14, candidates.size) + } + + @Test + fun `Candidate 'Open Link in New Tab' showFor displayed in correct cases`() { + val store = BrowserStore() + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + val openInNewTab = ContextMenuCandidate.createOpenInNewTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertTrue( + openInNewTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + openInNewTab.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + openInNewTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + openInNewTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + openInNewTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Open Link in New Tab' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val openInNewTab = ContextMenuCandidate.createOpenInNewTabCandidate( + testContext, + mock(), + mock(), + snackbarDelegate, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + openInNewTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + openInNewTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Open Link in New Tab' action properly executes for session with a contextId`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", contextId = "1"), + ), + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + val openInNewTab = ContextMenuCandidate.createOpenInNewTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + assertEquals("1", store.state.tabs.first().contextId) + + openInNewTab.action.invoke(store.state.tabs.first(), HitResult.UNKNOWN("https://firefox.com")) + store.waitUntilIdle() + + assertEquals(2, store.state.tabs.size) + assertEquals("https://firefox.com", store.state.tabs.last().content.url) + assertEquals("1", store.state.tabs.last().contextId) + } + + @Test + fun `Candidate 'Open Link in New Tab' action properly executes and shows snackbar`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org"), + ), + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + val openInNewTab = ContextMenuCandidate.createOpenInNewTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + assertFalse(snackbarDelegate.hasShownSnackbar) + + openInNewTab.action.invoke(store.state.tabs.first(), HitResult.UNKNOWN("https://firefox.com")) + store.waitUntilIdle() + + assertEquals(2, store.state.tabs.size) + assertTrue(snackbarDelegate.hasShownSnackbar) + assertNotNull(snackbarDelegate.lastActionListener) + assertEquals("https://firefox.com", store.state.tabs.last().content.url) + } + + @Test + fun `Candidate 'Open Link in New Tab' snackbar action works`() { + val store = BrowserStore( + middleware = EngineMiddleware.create( + engine = mock(), + scope = MainScope(), + ), + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla"), + ), + selectedTabId = "mozilla", + ), + ) + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + + val openInNewTab = ContextMenuCandidate.createOpenInNewTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + assertFalse(snackbarDelegate.hasShownSnackbar) + + openInNewTab.action.invoke(store.state.tabs.first(), HitResult.UNKNOWN("https://firefox.com")) + store.waitUntilIdle() + + assertEquals("https://www.mozilla.org", store.state.selectedTab!!.content.url) + + snackbarDelegate.lastActionListener!!.invoke(mock()) + store.waitUntilIdle() + + assertEquals("https://firefox.com", store.state.selectedTab!!.content.url) + } + + @Test + fun `Candidate 'Open Link in New Tab' action properly handles link with an image`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla"), + ), + selectedTabId = "mozilla", + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + + val openInNewTab = ContextMenuCandidate.createOpenInNewTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + + openInNewTab.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://www.mozilla_src.org", "https://www.mozilla_uri.org"), + ) + store.waitUntilIdle() + + assertEquals("https://www.mozilla_uri.org", store.state.tabs.last().content.url) + } + + @Test + fun `Candidate 'Open Link in Private Tab' showFor displayed in correct cases`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla"), + ), + selectedTabId = "mozilla", + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + val openInPrivateTab = ContextMenuCandidate.createOpenInPrivateTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertTrue( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Open Link in Private Tab' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val openInPrivateTab = ContextMenuCandidate.createOpenInPrivateTabCandidate( + testContext, + mock(), + mock(), + snackbarDelegate, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + openInPrivateTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Open Link in Private Tab' action properly executes and shows snackbar`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla"), + ), + selectedTabId = "mozilla", + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + val openInPrivateTab = ContextMenuCandidate.createOpenInPrivateTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + assertFalse(snackbarDelegate.hasShownSnackbar) + + openInPrivateTab.action.invoke(store.state.tabs.first(), HitResult.UNKNOWN("https://firefox.com")) + store.waitUntilIdle() + + assertEquals(2, store.state.tabs.size) + assertTrue(snackbarDelegate.hasShownSnackbar) + assertNotNull(snackbarDelegate.lastActionListener) + assertEquals("https://firefox.com", store.state.tabs.last().content.url) + } + + @Test + fun `Candidate 'Open Link in Private Tab' snackbar action works`() { + val store = BrowserStore( + middleware = EngineMiddleware.create( + engine = mock(), + scope = MainScope(), + ), + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla"), + ), + selectedTabId = "mozilla", + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + val openInPrivateTab = ContextMenuCandidate.createOpenInPrivateTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + assertFalse(snackbarDelegate.hasShownSnackbar) + + openInPrivateTab.action.invoke(store.state.tabs.first(), HitResult.UNKNOWN("https://firefox.com")) + store.waitUntilIdle() + + assertEquals("https://www.mozilla.org", store.state.selectedTab!!.content.url) + assertEquals(2, store.state.tabs.size) + + snackbarDelegate.lastActionListener!!.invoke(mock()) + store.waitUntilIdle() + + assertEquals("https://firefox.com", store.state.selectedTab!!.content.url) + } + + @Test + fun `Candidate 'Open Link in Private Tab' action properly handles link with an image`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla"), + ), + selectedTabId = "mozilla", + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + val openInPrivateTab = ContextMenuCandidate.createOpenInPrivateTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + openInPrivateTab.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://www.mozilla_src.org", "https://www.mozilla_uri.org"), + ) + store.waitUntilIdle() + assertEquals("https://www.mozilla_uri.org", store.state.tabs.last().content.url) + } + + @Test + fun `Candidate 'Open Image in New Tab'`() { + val store = BrowserStore( + middleware = EngineMiddleware.create( + engine = mock(), + scope = MainScope(), + ), + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla"), + ), + selectedTabId = "mozilla", + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + + val openImageInTab = ContextMenuCandidate.createOpenImageInNewTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + // showFor + + assertFalse( + openImageInTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + openImageInTab.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + openImageInTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertTrue( + openImageInTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + openImageInTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + + // action + + assertEquals(1, store.state.tabs.size) + assertFalse(snackbarDelegate.hasShownSnackbar) + + openImageInTab.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://firefox.com", "https://getpocket.com"), + ) + + store.waitUntilIdle() + + assertEquals(2, store.state.tabs.size) + assertFalse(store.state.tabs.last().content.private) + assertEquals("https://firefox.com", store.state.tabs.last().content.url) + assertTrue(snackbarDelegate.hasShownSnackbar) + assertNotNull(snackbarDelegate.lastActionListener) + + // Snackbar action + + assertEquals("https://www.mozilla.org", store.state.selectedTab!!.content.url) + + snackbarDelegate.lastActionListener!!.invoke(mock()) + store.waitUntilIdle() + + assertEquals("https://firefox.com", store.state.selectedTab!!.content.url) + } + + @Test + fun `Candidate 'Open Image in New Tab' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val openImageInTab = ContextMenuCandidate.createOpenImageInNewTabCandidate( + testContext, + mock(), + mock(), + snackbarDelegate, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + openImageInTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + openImageInTab.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Open Image in New Tab' opens in private tab if session is private`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + + val openImageInTab = ContextMenuCandidate.createOpenImageInNewTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + + openImageInTab.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://firefox.com", "https://getpocket.com"), + ) + store.waitUntilIdle() + + assertEquals(2, store.state.tabs.size) + assertTrue(store.state.tabs.last().content.private) + assertEquals("https://firefox.com", store.state.tabs.last().content.url) + } + + @Test + fun `Candidate 'Open Image in New Tab' opens with the session's contextId`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", contextId = "1"), + ), + selectedTabId = "mozilla", + ), + ) + + val tabsUseCases = TabsUseCases(store) + val parentView = CoordinatorLayout(testContext) + + val openImageInTab = ContextMenuCandidate.createOpenImageInNewTabCandidate( + testContext, + tabsUseCases, + parentView, + snackbarDelegate, + ) + + assertEquals(1, store.state.tabs.size) + assertEquals("1", store.state.tabs.first().contextId) + + openImageInTab.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://firefox.com", "https://getpocket.com"), + ) + store.waitUntilIdle() + + assertEquals(2, store.state.tabs.size) + assertEquals("https://firefox.com", store.state.tabs.last().content.url) + assertEquals("1", store.state.tabs.last().contextId) + } + + @Test + fun `Candidate 'Save image'`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + val saveImage = ContextMenuCandidate.createSaveImageCandidate( + testContext, + ContextMenuUseCases(store), + ) + + // showFor + + assertFalse( + saveImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + saveImage.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + saveImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertTrue( + saveImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + saveImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + + // action + + assertNull(store.state.tabs.first().content.download) + + saveImage.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC( + "https://www.mozilla.org/media/img/logos/firefox/logo-quantum.9c5e96634f92.png", + "https://firefox.com", + ), + ) + + store.waitUntilIdle() + + assertNotNull(store.state.tabs.first().content.download) + assertEquals( + "https://www.mozilla.org/media/img/logos/firefox/logo-quantum.9c5e96634f92.png", + store.state.tabs.first().content.download!!.url, + ) + assertTrue( + store.state.tabs.first().content.download!!.skipConfirmation, + ) + assertTrue( + store.state.tabs.first().content.download!!.private, + ) + } + + @Test + fun `Candidate 'Save image' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val saveImage = ContextMenuCandidate.createSaveImageCandidate( + testContext, + mock(), + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + saveImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + saveImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Save video and audio'`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + val saveVideoAudio = ContextMenuCandidate.createSaveVideoAudioCandidate( + testContext, + ContextMenuUseCases(store), + ) + + // showFor + + assertFalse( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertTrue( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + + assertTrue( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.AUDIO("https://www.mozilla.org"), + ), + ) + + // action + + assertNull(store.state.tabs.first().content.download) + + saveVideoAudio.action.invoke( + store.state.tabs.first(), + HitResult.AUDIO("https://developer.mozilla.org/media/examples/t-rex-roar.mp3"), + ) + + store.waitUntilIdle() + + assertNotNull(store.state.tabs.first().content.download) + assertEquals( + "https://developer.mozilla.org/media/examples/t-rex-roar.mp3", + store.state.tabs.first().content.download!!.url, + ) + assertTrue( + store.state.tabs.first().content.download!!.skipConfirmation, + ) + + assertTrue( + store.state.tabs.first().content.download!!.private, + ) + } + + @Test + fun `Candidate 'Save video and audio' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val saveVideoAudio = ContextMenuCandidate.createSaveVideoAudioCandidate( + testContext, + mock(), + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + + assertFalse( + saveVideoAudio.showFor( + createTab("https://www.mozilla.org"), + HitResult.AUDIO("https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'download link'`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + val downloadLink = ContextMenuCandidate.createDownloadLinkCandidate( + testContext, + ContextMenuUseCases(store), + ) + + // showFor + + assertTrue( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + downloadLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.PHONE("https://www.mozilla.org"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.EMAIL("https://www.mozilla.org"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.GEO("https://www.mozilla.org"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org/firefox/products.html"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org/firefox/products.htm"), + ), + ) + + // action + + assertNull(store.state.tabs.first().content.download) + + downloadLink.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC( + "https://www.mozilla.org/media/img/logos/firefox/logo-quantum.9c5e96634f92.png", + "https://www.mozilla.org/en-US/privacy-policy.pdf", + ), + ) + + store.waitUntilIdle() + + assertNotNull(store.state.tabs.first().content.download) + assertEquals( + "https://www.mozilla.org/en-US/privacy-policy.pdf", + store.state.tabs.first().content.download!!.url, + ) + assertTrue( + store.state.tabs.first().content.download!!.skipConfirmation, + ) + + assertTrue(store.state.tabs.first().content.download!!.private) + } + + @Test + fun `Candidate 'download link' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val downloadLink = ContextMenuCandidate.createDownloadLinkCandidate( + testContext, + mock(), + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + downloadLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Get link for image, video, audio gets title if title is set`() { + val titleString = "test title" + + val hitResultImage = HitResult.IMAGE("https://www.mozilla.org", titleString) + var title = hitResultImage.getLink() + assertEquals(titleString, title) + + val hitResultVideo = HitResult.VIDEO("https://www.mozilla.org", titleString) + title = hitResultVideo.getLink() + assertEquals(titleString, title) + + val hitResultAudio = HitResult.AUDIO("https://www.mozilla.org", titleString) + title = hitResultAudio.getLink() + assertEquals(titleString, title) + } + + @Test + fun `Get link for image, video, audio gets URL if title is blank`() { + val titleString = " " + val url = "https://www.mozilla.org" + + val hitResultImage = HitResult.IMAGE(url, titleString) + var title = hitResultImage.getLink() + assertEquals(url, title) + + val hitResultVideo = HitResult.VIDEO(url, titleString) + title = hitResultVideo.getLink() + assertEquals(url, title) + + val hitResultAudio = HitResult.AUDIO(url, titleString) + title = hitResultAudio.getLink() + assertEquals(url, title) + } + + @Test + fun `Get link for image, video, audio gets URL if title is null`() { + val titleString = null + val url = "https://www.mozilla.org" + + val hitResultImage = HitResult.IMAGE(url, titleString) + var title = hitResultImage.getLink() + assertEquals(url, title) + + val hitResultVideo = HitResult.VIDEO(url, titleString) + title = hitResultVideo.getLink() + assertEquals(url, title) + + val hitResultAudio = HitResult.AUDIO(url, titleString) + title = hitResultAudio.getLink() + assertEquals(url, title) + } + + @Test + fun `Get link for image gets 'image' title if title is null and URL is longer than 2500 characters`() { + val titleString = null + val replacementString = "image" + val url = "1".repeat(ContextMenuCandidate.MAX_TITLE_LENGTH + 1) + + val hitResultImage = HitResult.IMAGE(url, titleString) + val title = hitResultImage.getLink() + assertEquals(replacementString, title) + } + + @Test + fun `Get link for image gets URL if title is null and URL is not longer than 2500 characters`() { + val titleString = null + val url = "1".repeat(ContextMenuCandidate.MAX_TITLE_LENGTH) + + val hitResultImage = HitResult.IMAGE(url, titleString) + val title = hitResultImage.getLink() + assertEquals(url, title) + } + + @Test + fun `Candidate 'Share Link'`() { + val context = spy(testContext) + + val shareLink = ContextMenuCandidate.createShareLinkCandidate(context) + + // showFor + + assertTrue( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + shareLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertTrue( + shareLink.showFor( + createTab("test://www.mozilla.org"), + HitResult.UNKNOWN("test://www.mozilla.org"), + ), + ) + + assertTrue( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertTrue( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + + // action + + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + shareLink.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://firefox.com", "https://getpocket.com"), + ) + + verify(context).startActivity(any()) + } + + @Test + fun `Candidate 'Share Link' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val shareLink = ContextMenuCandidate.createShareLinkCandidate( + testContext, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + shareLink.showFor( + createTab("test://www.mozilla.org"), + HitResult.UNKNOWN("test://www.mozilla.org"), + ), + ) + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + shareLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Share image'`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf(TabSessionState("123", ContentState(url = "https://www.mozilla.org"))), + ), + ) + val context = spy(testContext) + + val usecases = spy(ContextMenuUseCases(store)) + val shareUsecase: ContextMenuUseCases.InjectShareInternetResourceUseCase = mock() + doReturn(shareUsecase).`when`(usecases).injectShareFromInternet + val shareImage = ContextMenuCandidate.createShareImageCandidate(context, usecases) + val shareStateCaptor = argumentCaptor() + // showFor + + assertTrue( + shareImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertTrue( + shareImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + shareImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.AUDIO("https://www.mozilla.org"), + ), + ) + + // action + + shareImage.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://firefox.com", "https://getpocket.com"), + ) + + verify(shareUsecase).invoke(eq("123"), shareStateCaptor.capture()) + assertEquals("https://firefox.com", shareStateCaptor.value.url) + assertEquals(store.state.tabs.first().content.private, shareStateCaptor.value.private) + } + + @Test + fun `Candidate 'Share image' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val shareImage = ContextMenuCandidate.createShareImageCandidate( + testContext, + mock(), + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + shareImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + shareImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Copy image'`() { + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf(TabSessionState("123", ContentState(url = "https://www.mozilla.org"))), + ), + ) + val context = spy(testContext) + + val useCases = spy(ContextMenuUseCases(store)) + val copyUseCase: ContextMenuUseCases.InjectCopyInternetResourceUseCase = mock() + doReturn(copyUseCase).`when`(useCases).injectCopyFromInternet + val copyImage = ContextMenuCandidate.createCopyImageCandidate(context, useCases) + val shareStateCaptor = argumentCaptor() + + // showFor + + assertTrue( + copyImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertTrue( + copyImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + copyImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.AUDIO("https://www.mozilla.org"), + ), + ) + + // action + + copyImage.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://firefox.com", "https://getpocket.com"), + ) + + verify(copyUseCase).invoke(eq("123"), shareStateCaptor.capture()) + assertEquals("https://firefox.com", shareStateCaptor.value.url) + assertEquals(store.state.tabs.first().content.private, shareStateCaptor.value.private) + } + + @Test + fun `Candidate 'Copy image' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val copyImage = ContextMenuCandidate.createCopyImageCandidate( + testContext, + mock(), + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + copyImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + copyImage.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Copy Link'`() { + val parentView = CoordinatorLayout(testContext) + + val copyLink = ContextMenuCandidate.createCopyLinkCandidate( + testContext, + parentView, + snackbarDelegate, + ) + + // showFor + + assertTrue( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + copyLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertTrue( + copyLink.showFor( + createTab("test://www.mozilla.org"), + HitResult.UNKNOWN("test://www.mozilla.org"), + ), + ) + + assertTrue( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertTrue( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + + // action + + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + copyLink.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://firefox.com", "https://getpocket.com"), + ) + + assertTrue(snackbarDelegate.hasShownSnackbar) + + val clipboardManager = testContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + assertEquals( + "https://getpocket.com", + clipboardManager.primaryClip!!.getItemAt(0).text, + ) + } + + @Test + fun `Candidate 'Copy Link' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val copyLink = ContextMenuCandidate.createCopyLinkCandidate( + testContext, + mock(), + snackbarDelegate, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + copyLink.showFor( + createTab("test://www.mozilla.org"), + HitResult.UNKNOWN("test://www.mozilla.org"), + ), + ) + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + copyLink.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Copy Image Location'`() { + val parentView = CoordinatorLayout(testContext) + + val copyImageLocation = ContextMenuCandidate.createCopyImageLocationCandidate( + testContext, + parentView, + snackbarDelegate, + ) + + // showFor + + assertFalse( + copyImageLocation.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + copyImageLocation.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertTrue( + copyImageLocation.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertTrue( + copyImageLocation.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + + assertFalse( + copyImageLocation.showFor( + createTab("https://www.mozilla.org"), + HitResult.VIDEO("https://www.mozilla.org"), + ), + ) + + // action + + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + copyImageLocation.action.invoke( + store.state.tabs.first(), + HitResult.IMAGE_SRC("https://firefox.com", "https://getpocket.com"), + ) + + assertTrue(snackbarDelegate.hasShownSnackbar) + + val clipboardManager = testContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + assertEquals( + "https://firefox.com", + clipboardManager.primaryClip!!.getItemAt(0).text, + ) + } + + @Test + fun `Candidate 'Copy Image Location' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val copyImageLocation = ContextMenuCandidate.createCopyImageLocationCandidate( + testContext, + mock(), + snackbarDelegate, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + copyImageLocation.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE_SRC("https://www.mozilla.org", "https://www.mozilla.org"), + ), + ) + + assertFalse( + copyImageLocation.showFor( + createTab("https://www.mozilla.org"), + HitResult.IMAGE("https://www.mozilla.org"), + ), + ) + } + + @Test + fun `Candidate 'Open in external app'`() { + val tab = createTab("https://www.mozilla.org") + val getAppLinkRedirectMock: AppLinksUseCases.GetAppLinkRedirect = mock() + + doReturn( + AppLinkRedirect(mock(), null, null), + ).`when`(getAppLinkRedirectMock).invoke(eq("https://www.example.com")) + + doReturn( + AppLinkRedirect(null, null, mock()), + ).`when`(getAppLinkRedirectMock).invoke(eq("intent:www.example.com#Intent;scheme=https;package=org.mozilla.fenix;end")) + + doReturn( + AppLinkRedirect(null, null, null), + ).`when`(getAppLinkRedirectMock).invoke(eq("https://www.otherexample.com")) + + // This mock exists only to verify that it was called + val openAppLinkRedirectMock: AppLinksUseCases.OpenAppLinkRedirect = mock() + + val appLinksUseCasesMock: AppLinksUseCases = mock() + doReturn(getAppLinkRedirectMock).`when`(appLinksUseCasesMock).appLinkRedirectIncludeInstall + doReturn(openAppLinkRedirectMock).`when`(appLinksUseCasesMock).openAppLink + + val openLinkInExternalApp = ContextMenuCandidate.createOpenInExternalAppCandidate( + testContext, + appLinksUseCasesMock, + ) + + // showFor + + assertTrue( + openLinkInExternalApp.showFor( + tab, + HitResult.UNKNOWN("https://www.example.com"), + ), + ) + + assertTrue( + openLinkInExternalApp.showFor( + tab, + HitResult.UNKNOWN("intent:www.example.com#Intent;scheme=https;package=org.mozilla.fenix;end"), + ), + ) + + assertTrue( + openLinkInExternalApp.showFor( + tab, + HitResult.VIDEO("https://www.example.com"), + ), + ) + + assertTrue( + openLinkInExternalApp.showFor( + tab, + HitResult.AUDIO("https://www.example.com"), + ), + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.UNKNOWN("https://www.otherexample.com"), + ), + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.VIDEO("https://www.otherexample.com"), + ), + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.AUDIO("https://www.otherexample.com"), + ), + ) + + // action + + openLinkInExternalApp.action.invoke( + tab, + HitResult.UNKNOWN("https://www.example.com"), + ) + + openLinkInExternalApp.action.invoke( + tab, + HitResult.UNKNOWN("intent:www.example.com#Intent;scheme=https;package=org.mozilla.fenix;end"), + ) + + openLinkInExternalApp.action.invoke( + tab, + HitResult.UNKNOWN("https://www.otherexample.com"), + ) + + verify(openAppLinkRedirectMock, times(2)).invoke(any(), anyBoolean(), any()) + } + + @Test + fun `Candidate 'Open in external app' allows for an additional validation for it to be shown`() { + val tab = createTab("https://www.mozilla.org") + val getAppLinkRedirectMock: AppLinksUseCases.GetAppLinkRedirect = mock() + doReturn( + AppLinkRedirect(mock(), null, null), + ).`when`(getAppLinkRedirectMock).invoke(eq("https://www.example.com")) + doReturn( + AppLinkRedirect(null, null, mock()), + ).`when`(getAppLinkRedirectMock).invoke(eq("intent:www.example.com#Intent;scheme=https;package=org.mozilla.fenix;end")) + val openAppLinkRedirectMock: AppLinksUseCases.OpenAppLinkRedirect = mock() + val appLinksUseCasesMock: AppLinksUseCases = mock() + doReturn(getAppLinkRedirectMock).`when`(appLinksUseCasesMock).appLinkRedirectIncludeInstall + doReturn(openAppLinkRedirectMock).`when`(appLinksUseCasesMock).openAppLink + val additionalValidation = { _: SessionState, _: HitResult -> false } + val openLinkInExternalApp = ContextMenuCandidate.createOpenInExternalAppCandidate( + testContext, + appLinksUseCasesMock, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.UNKNOWN("https://www.example.com"), + ), + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.UNKNOWN("intent:www.example.com#Intent;scheme=https;package=org.mozilla.fenix;end"), + ), + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.VIDEO("https://www.example.com"), + ), + ) + + assertFalse( + openLinkInExternalApp.showFor( + tab, + HitResult.AUDIO("https://www.example.com"), + ), + ) + } + + @Test + fun `Candidate 'Copy email address'`() { + val parentView = CoordinatorLayout(testContext) + + val copyEmailAddress = ContextMenuCandidate.createCopyEmailAddressCandidate( + testContext, + parentView, + snackbarDelegate, + ) + + // showFor + + assertTrue( + copyEmailAddress.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com"), + ), + ) + + assertTrue( + copyEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com"), + ), + ) + + assertFalse( + copyEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + copyEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("example@example.com"), + ), + ) + + // action + + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + copyEmailAddress.action.invoke( + store.state.tabs.first(), + HitResult.UNKNOWN("mailto:example@example.com"), + ) + + assertTrue(snackbarDelegate.hasShownSnackbar) + + val clipboardManager = testContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + assertEquals( + "example@example.com", + clipboardManager.primaryClip!!.getItemAt(0).text, + ) + } + + @Test + fun `Candidate 'Copy email address' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val copyEmailAddress = ContextMenuCandidate.createCopyEmailAddressCandidate( + testContext, + mock(), + snackbarDelegate, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + copyEmailAddress.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com"), + ), + ) + + assertFalse( + copyEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com"), + ), + ) + } + + @Test + fun `Candidate 'Share email address'`() { + val context = spy(testContext) + + val shareEmailAddress = ContextMenuCandidate.createShareEmailAddressCandidate(context) + + // showFor + + assertTrue( + shareEmailAddress.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com"), + ), + ) + + assertTrue( + shareEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com"), + ), + ) + + assertFalse( + shareEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + shareEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("example@example.com"), + ), + ) + + // action + + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + shareEmailAddress.action.invoke( + store.state.tabs.first(), + HitResult.UNKNOWN("mailto:example@example.com"), + ) + + verify(context).startActivity(any()) + } + + @Test + fun `Candidate 'Share email address' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val shareEmailAddress = ContextMenuCandidate.createShareEmailAddressCandidate( + testContext, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + shareEmailAddress.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com"), + ), + ) + + assertFalse( + shareEmailAddress.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com"), + ), + ) + } + + @Test + fun `Candidate 'Add to contacts'`() { + val context = spy(testContext) + + val addToContacts = ContextMenuCandidate.createAddContactCandidate(context) + + // showFor + + assertTrue( + addToContacts.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com"), + ), + ) + + assertTrue( + addToContacts.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com"), + ), + ) + + assertFalse( + addToContacts.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ) + + assertFalse( + addToContacts.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("example@example.com"), + ), + ) + + // action + + val store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "mozilla", private = true), + ), + selectedTabId = "mozilla", + ), + ) + + addToContacts.action.invoke( + store.state.tabs.first(), + HitResult.UNKNOWN("mailto:example@example.com"), + ) + + verify(context).startActivity(any()) + } + + @Test + fun `Candidate 'Add to contacts' allows for an additional validation for it to be shown`() { + val additionalValidation = { _: SessionState, _: HitResult -> false } + val addToContacts = ContextMenuCandidate.createAddContactCandidate( + testContext, + additionalValidation, + ) + + // By default in the below cases the candidate will be shown. 'additionalValidation' changes that. + + assertFalse( + addToContacts.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("mailto:example@example.com"), + ), + ) + + assertFalse( + addToContacts.showFor( + createTab("https://www.mozilla.org", private = true), + HitResult.UNKNOWN("mailto:example.com"), + ), + ) + } + + @Test + fun `GIVEN SessionState with null EngineSession WHEN isUrlSchemeAllowed is called THEN it returns true`() { + val sessionState = TabSessionState( + content = mock(), + engineState = EngineState(engineSession = null), + ) + + assertTrue(sessionState.isUrlSchemeAllowed("http://mozilla.org")) + } + + @Test + fun `GIVEN SessionState with no blocked url schemes WHEN isUrlSchemeAllowed is called THEN it returns true`() { + val noBlockedUrlSchemesEngineSession = Mockito.mock(EngineSession::class.java) + doReturn(emptyList()).`when`(noBlockedUrlSchemesEngineSession).getBlockedSchemes() + val sessionState = TabSessionState( + content = mock(), + engineState = EngineState(engineSession = noBlockedUrlSchemesEngineSession), + ) + + assertTrue(sessionState.isUrlSchemeAllowed("http://mozilla.org")) + } + + @Test + fun `GIVEN SessionState with blocked url schemes WHEN isUrlSchemeAllowed is called THEN it returns false if the url has that scheme`() { + val engineSessionWithBlockedUrlScheme = Mockito.mock(EngineSession::class.java) + doReturn(listOf("http")).`when`(engineSessionWithBlockedUrlScheme).getBlockedSchemes() + val sessionState = TabSessionState( + content = mock(), + engineState = EngineState(engineSession = engineSessionWithBlockedUrlScheme), + ) + + assertFalse(sessionState.isUrlSchemeAllowed("http://mozilla.org")) + assertFalse(sessionState.isUrlSchemeAllowed("hTtP://mozilla.org")) + assertFalse(sessionState.isUrlSchemeAllowed("HttP://www.mozilla.org")) + assertTrue(sessionState.isUrlSchemeAllowed("www.mozilla.org")) + assertTrue(sessionState.isUrlSchemeAllowed("https://mozilla.org")) + assertTrue(sessionState.isUrlSchemeAllowed("mozilla.org")) + assertTrue(sessionState.isUrlSchemeAllowed("/mozilla.org")) + assertTrue(sessionState.isUrlSchemeAllowed("content://http://mozilla.org")) + } + + @Test + fun `GIVEN SessionState with blocked url schemes WHEN isUrlSchemeAllowed is called THEN it returns true if the url does not have that scheme`() { + val engineSessionWithBlockedUrlScheme = Mockito.mock(EngineSession::class.java) + doReturn(listOf("http")).`when`(engineSessionWithBlockedUrlScheme).getBlockedSchemes() + val sessionState = TabSessionState( + content = mock(), + engineState = EngineState(engineSession = engineSessionWithBlockedUrlScheme), + ) + + assertTrue(sessionState.isUrlSchemeAllowed("https://mozilla.org")) + } +} + +private class TestSnackbarDelegate : SnackbarDelegate { + var hasShownSnackbar = false + var lastActionListener: ((v: View) -> Unit)? = null + + override fun show(snackBarParentView: View, text: Int, duration: Int, action: Int, listener: ((v: View) -> Unit)?) { + hasShownSnackbar = true + lastActionListener = listener + } +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFeatureTest.kt b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFeatureTest.kt new file mode 100644 index 0000000000..057d020108 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFeatureTest.kt @@ -0,0 +1,403 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import android.view.HapticFeedbackConstants +import android.view.View +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentTransaction +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.state.action.ContentAction +import mozilla.components.browser.state.action.TabListAction +import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.EngineView +import mozilla.components.concept.engine.HitResult +import mozilla.components.support.base.Component +import mozilla.components.support.base.facts.Action +import mozilla.components.support.base.facts.processor.CollectionProcessor +import mozilla.components.support.test.any +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.libstate.ext.waitUntilIdle +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` + +@RunWith(AndroidJUnit4::class) +class ContextMenuFeatureTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val dispatcher = coroutinesTestRule.testDispatcher + + private lateinit var store: BrowserStore + + @Before + fun setUp() { + store = BrowserStore( + BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "test-tab"), + ), + selectedTabId = "test-tab", + ), + ) + } + + @Test + fun `New HitResult for selected session will cause fragment transaction`() { + val fragmentManager = mockFragmentManager() + + val (engineView, view) = mockEngineView() + + val feature = ContextMenuFeature( + fragmentManager, + store, + ContextMenuCandidate.defaultCandidates(testContext, mock(), mock(), mock()), + engineView, + mock(), + ) + + feature.start() + + store.dispatch( + ContentAction.UpdateHitResultAction( + "test-tab", + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ).joinBlocking() + + dispatcher.scheduler.advanceUntilIdle() + + verify(fragmentManager).beginTransaction() + verify(view).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + + @Test + fun `New HitResult for selected session will not cause fragment transaction if feature is stopped`() { + val fragmentManager = mockFragmentManager() + + val (engineView, view) = mockEngineView() + + val feature = ContextMenuFeature( + fragmentManager, + store, + ContextMenuCandidate.defaultCandidates(testContext, mock(), mock(), mock()), + engineView, + mock(), + ) + + feature.start() + feature.stop() + + store.dispatch( + ContentAction.UpdateHitResultAction( + "test-tab", + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ).joinBlocking() + + dispatcher.scheduler.advanceUntilIdle() + + verify(fragmentManager, never()).beginTransaction() + verify(view, never()).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + + @Test + fun `Feature will re-attach to already existing fragment`() { + val fragment: ContextMenuFragment = mock() + doReturn("test-tab").`when`(fragment).sessionId + + val fragmentManager: FragmentManager = mock() + doReturn(fragment).`when`(fragmentManager).findFragmentByTag(any()) + + val (engineView, view) = mockEngineView() + + store.dispatch( + ContentAction.UpdateHitResultAction( + "test-tab", + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ).joinBlocking() + + val feature = ContextMenuFeature( + fragmentManager, + store, + ContextMenuCandidate.defaultCandidates(testContext, mock(), mock(), mock()), + engineView, + mock(), + ) + + feature.start() + + dispatcher.scheduler.advanceUntilIdle() + + verify(fragment).feature = feature + verify(view, never()).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + + @Test + fun `Already existing fragment will be removed if session has no HitResult set anymore`() { + val fragment: ContextMenuFragment = mock() + doReturn("test-tab").`when`(fragment).sessionId + + val transaction: FragmentTransaction = mock() + + val fragmentManager: FragmentManager = mock() + doReturn(fragment).`when`(fragmentManager).findFragmentByTag(any()) + doReturn(transaction).`when`(fragmentManager).beginTransaction() + doReturn(transaction).`when`(transaction).remove(fragment) + + val (engineView, view) = mockEngineView() + + val feature = ContextMenuFeature( + fragmentManager, + store, + ContextMenuCandidate.defaultCandidates(testContext, mock(), mock(), mock()), + engineView, + mock(), + ) + + feature.start() + + dispatcher.scheduler.advanceUntilIdle() + + verify(fragmentManager).beginTransaction() + verify(transaction).remove(fragment) + + verify(view, never()).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + + fun `Already existing fragment will be removed if session does not exist anymore`() { + val fragment: ContextMenuFragment = mock() + doReturn("test-tab").`when`(fragment).sessionId + + val transaction: FragmentTransaction = mock() + + val fragmentManager: FragmentManager = mock() + doReturn(fragment).`when`(fragmentManager).findFragmentByTag(any()) + doReturn(transaction).`when`(fragmentManager).beginTransaction() + doReturn(transaction).`when`(transaction).remove(fragment) + + val (engineView, view) = mockEngineView() + + val feature = ContextMenuFeature( + fragmentManager, + store, + ContextMenuCandidate.defaultCandidates(testContext, mock(), mock(), mock()), + engineView, + mock(), + ) + + store.dispatch(TabListAction.RemoveTabAction("test-tab")) + .joinBlocking() + + feature.start() + + dispatcher.scheduler.advanceUntilIdle() + + verify(fragmentManager).beginTransaction() + verify(transaction).remove(fragment) + + verify(view, never()).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + + @Test + fun `No dialog will be shown if no item wants to be shown`() { + val fragmentManager = mockFragmentManager() + + val candidate = ContextMenuCandidate( + id = "test-id", + label = "Test Item", + showFor = { _, _ -> false }, + action = { _, _ -> Unit }, + ) + + val (engineView, view) = mockEngineView() + + val feature = ContextMenuFeature( + fragmentManager, + store, + listOf(candidate), + engineView, + ContextMenuUseCases(mock()), + ) + + feature.showContextMenu( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN("https://www.mozilla.org"), + ) + + verify(fragmentManager, never()).beginTransaction() + verify(view, never()).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + + @Test + fun `Cancelling context menu item will consume HitResult`() { + store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "test-tab"), + ), + ), + ) + + store.dispatch( + ContentAction.UpdateHitResultAction( + "test-tab", + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ).joinBlocking() + + val (engineView, _) = mockEngineView() + + val feature = ContextMenuFeature( + mockFragmentManager(), + store, + ContextMenuCandidate.defaultCandidates(testContext, mock(), mock(), mock()), + engineView, + ContextMenuUseCases(store), + ) + + assertNotNull(store.state.findTab("test-tab")!!.content.hitResult) + + feature.onMenuCancelled("test-tab") + + store.waitUntilIdle() + dispatcher.scheduler.advanceUntilIdle() + + assertNull(store.state.findTab("test-tab")!!.content.hitResult) + } + + @Test + fun `Selecting context menu item will invoke action of candidate and consume HitResult`() { + store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "test-tab"), + ), + ), + ) + + store.dispatch( + ContentAction.UpdateHitResultAction( + "test-tab", + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ).joinBlocking() + + val (engineView, view) = mockEngineView() + var actionInvoked = false + + val candidate = ContextMenuCandidate( + id = "test-id", + label = "Test Item", + showFor = { _, _ -> true }, + action = { _, _ -> actionInvoked = true }, + ) + + val feature = ContextMenuFeature( + mockFragmentManager(), + store, + listOf(candidate), + engineView, + ContextMenuUseCases(store), + ) + + store.waitUntilIdle() + dispatcher.scheduler.advanceUntilIdle() + + assertNotNull(store.state.findTab("test-tab")!!.content.hitResult) + assertFalse(actionInvoked) + + feature.onMenuItemSelected("test-tab", "test-id") + + store.waitUntilIdle() + dispatcher.scheduler.advanceUntilIdle() + + assertNull(store.state.findTab("test-tab")!!.content.hitResult) + assertTrue(actionInvoked) + verify(view, never()).performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + + @Test + fun `Selecting context menu item will emit a click fact`() { + store = BrowserStore( + initialState = BrowserState( + tabs = listOf( + createTab("https://www.mozilla.org", id = "test-tab"), + ), + ), + ) + + store.dispatch( + ContentAction.UpdateHitResultAction( + "test-tab", + HitResult.UNKNOWN("https://www.mozilla.org"), + ), + ).joinBlocking() + + val (engineView, _) = mockEngineView() + val candidate = ContextMenuCandidate( + id = "test-id", + label = "Test Item", + showFor = { _, _ -> true }, + action = { _, _ -> }, // noop + ) + + val feature = ContextMenuFeature( + mockFragmentManager(), + store, + listOf(candidate), + engineView, + ContextMenuUseCases(store), + ) + + CollectionProcessor.withFactCollection { facts -> + feature.onMenuItemSelected("test-tab", candidate.id) + + assertEquals(1, facts.size) + + val fact = facts[0] + assertEquals(Component.FEATURE_CONTEXTMENU, fact.component) + assertEquals(Action.CLICK, fact.action) + assertEquals("item", fact.item) + assertEquals("test-id", fact.metadata?.get("item")) + } + } + + private fun mockFragmentManager(): FragmentManager { + val fragmentManager: FragmentManager = mock() + + val transaction: FragmentTransaction = mock() + doReturn(transaction).`when`(fragmentManager).beginTransaction() + + return fragmentManager + } + + private fun mockEngineView(): Pair { + val actualView: View = mock() + + val engineView = mock().also { + `when`(it.asView()).thenReturn(actualView) + } + + return Pair(engineView, actualView) + } +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFragmentTest.kt b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFragmentTest.kt new file mode 100644 index 0000000000..b3e312c4c9 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuFragmentTest.kt @@ -0,0 +1,347 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import android.view.LayoutInflater +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.state.state.createTab +import mozilla.components.concept.engine.HitResult +import mozilla.components.support.test.any +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify + +@RunWith(AndroidJUnit4::class) +class ContextMenuFragmentTest { + @Test + fun `Build dialog`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val title = "Hello World" + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + doReturn(testContext).`when`(fragment).requireContext() + + val dialog = fragment.onCreateDialog(null) + + assertNotNull(dialog) + + verify(fragment).createDialogTitleView(any()) + verify(fragment).createDialogContentView(any()) + } + + @Test + fun `Dialog title view`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val title = "Hello World" + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + val inflater = LayoutInflater.from(testContext) + val view = fragment.createDialogTitleView(inflater) + + assertEquals( + "Hello World", + view.findViewById(R.id.titleView).text, + ) + } + + @Test + fun `CLicking title view expands title`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val title = "Hello World" + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + val inflater = LayoutInflater.from(testContext) + val view = fragment.createDialogTitleView(inflater) + val titleView = view.findViewById(R.id.titleView) + + titleView.performClick() + + assertEquals( + 15, + titleView.maxLines, + ) + } + + @Test + fun `Dialog content view`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val title = "Hello World" + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + doReturn(testContext).`when`(fragment).requireContext() + + val inflater = LayoutInflater.from(testContext) + val view = fragment.createDialogContentView(inflater) + + val adapter = view.findViewById(R.id.recyclerView).adapter as ContextMenuAdapter + + assertEquals(3, adapter.itemCount) + + val parent = LinearLayout(testContext) + + val holder = adapter.onCreateViewHolder(parent, 0) + + adapter.bindViewHolder(holder, 0) + assertEquals("Item A", holder.labelView.text) + + adapter.bindViewHolder(holder, 1) + assertEquals("Item B", holder.labelView.text) + + adapter.bindViewHolder(holder, 2) + assertEquals("Item C", holder.labelView.text) + } + + @Test + fun `Clicking context menu item notifies fragment`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val title = "Hello World" + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + doReturn(testContext).`when`(fragment).requireContext() + doNothing().`when`(fragment).dismiss() + + val inflater = LayoutInflater.from(testContext) + val view = fragment.createDialogContentView(inflater) + + val adapter = view.findViewById(R.id.recyclerView).adapter as ContextMenuAdapter + val holder = adapter.onCreateViewHolder(LinearLayout(testContext), 0) + adapter.bindViewHolder(holder, 0) + + holder.labelView.performClick() + + verify(fragment).onItemSelected(0) + } + + @Test + fun `On selection fragment notifies feature and dismisses dialog`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val title = "Hello World" + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val feature: ContextMenuFeature = mock() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + fragment.feature = feature + doNothing().`when`(fragment).dismiss() + + fragment.onItemSelected(0) + + verify(feature).onMenuItemSelected(tab.id, "A") + verify(fragment).dismiss() + } + + @Test + fun `Fragment shows correct title for IMAGE HitResult`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val titleString = "test title" + val additionalNote = "Additional note" + + val hitResult = HitResult.IMAGE("https://www.mozilla.org", titleString) + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals(titleString, fragment.title) + } + + @Test + fun `Fragment shows src as title for IMAGE HitResult with blank title`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val titleString = " " + val additionalNote = "Additional note" + + val hitResult = HitResult.IMAGE("https://www.mozilla.org", titleString) + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("https://www.mozilla.org", fragment.title) + } + + @Test + fun `Fragment shows src as title for IMAGE HitResult with null title`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val hitResult = HitResult.IMAGE("https://www.mozilla.org") + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("https://www.mozilla.org", fragment.title) + } + + @Test + fun `Fragment shows uri as title for IMAGE_SRC HitResult`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val hitResult = HitResult.IMAGE_SRC("https://www.mozilla.org", "https://another.com") + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("https://another.com", fragment.title) + } + + @Test + fun `Fragment shows src as title for UNKNOWN HitResult`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val hitResult = HitResult.UNKNOWN("https://www.mozilla.org") + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("https://www.mozilla.org", fragment.title) + } + + @Test + fun `Fragment shows src as title for AUDIO HitResult with blank title`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val titleString = " " + val additionalNote = "Additional note" + + val hitResult = HitResult.AUDIO("https://www.mozilla.org", titleString) + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("https://www.mozilla.org", fragment.title) + } + + @Test + fun `Fragment shows src as title for AUDIO HitResult with null title`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val hitResult = HitResult.AUDIO("https://www.mozilla.org") + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("https://www.mozilla.org", fragment.title) + } + + @Test + fun `Fragment shows src as title for VIDEO HitResult with blank title`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val titleString = " " + val additionalNote = "Additional note" + + val hitResult = HitResult.VIDEO("https://www.mozilla.org", titleString) + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("https://www.mozilla.org", fragment.title) + } + + @Test + fun `Fragment shows src as title for VIDEO HitResult with null title`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val hitResult = HitResult.VIDEO("https://www.mozilla.org") + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("https://www.mozilla.org", fragment.title) + } + + @Test + fun `Fragment shows about blank as title for EMAIL HitResult`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val hitResult = HitResult.EMAIL("https://www.mozilla.org") + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("about:blank", fragment.title) + } + + @Test + fun `Fragment shows about blank as title for GEO HitResult`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val hitResult = HitResult.GEO("https://www.mozilla.org") + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("about:blank", fragment.title) + } + + @Test + fun `Fragment shows about blank as title for PHONE HitResult`() { + val ids = listOf("A", "B", "C") + val labels = listOf("Item A", "Item B", "Item C") + val tab = createTab("https://www.mozilla.org") + val additionalNote = "Additional note" + + val hitResult = HitResult.PHONE("https://www.mozilla.org") + val title = hitResult.getLink() + + val fragment = spy(ContextMenuFragment.create(tab, title, ids, labels, additionalNote)) + + assertEquals("about:blank", fragment.title) + } +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/DefaultSelectionActionDelegateTest.kt b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/DefaultSelectionActionDelegateTest.kt new file mode 100644 index 0000000000..7f034889e7 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/DefaultSelectionActionDelegateTest.kt @@ -0,0 +1,267 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.contextmenu + +import android.content.res.Resources +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.feature.search.SearchAdapter +import mozilla.components.support.base.facts.Fact +import mozilla.components.support.base.facts.FactProcessor +import mozilla.components.support.base.facts.Facts +import mozilla.components.support.test.mock +import mozilla.components.support.test.whenever +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyString +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@RunWith(AndroidJUnit4::class) +class DefaultSelectionActionDelegateTest { + + val selectedRegularText = "mozilla" + val selectedEmailText = "test@mozilla.org" + val selectedPhoneText = "555-5555" + var lambdaValue: String? = null + val shareClicked: (String) -> Unit = { lambdaValue = it } + val emailClicked: (String) -> Unit = { lambdaValue = it } + val phoneClicked: (String) -> Unit = { lambdaValue = it } + + @Before + fun setup() { + lambdaValue = null + } + + @Test + fun `are non-private regular actions available`() { + val searchAdapter = mock { + whenever(isPrivateSession()).thenReturn(false) + } + val delegate = DefaultSelectionActionDelegate( + searchAdapter, + getTestResources(), + shareClicked, + emailClicked, + phoneClicked, + ) + + assertTrue(delegate.isActionAvailable(SEARCH, selectedRegularText)) + assertTrue(delegate.isActionAvailable(SHARE, selectedRegularText)) + assertFalse(delegate.isActionAvailable(SEARCH_PRIVATELY, selectedRegularText)) + assertFalse(delegate.isActionAvailable(EMAIL, selectedRegularText)) + assertFalse(delegate.isActionAvailable(CALL, selectedRegularText)) + } + + @Test + fun `are non-private non-share actions available`() { + val searchAdapter = mock { + whenever(isPrivateSession()).thenReturn(false) + } + val delegate = DefaultSelectionActionDelegate( + searchAdapter, + getTestResources(), + ) + + assertTrue(delegate.isActionAvailable(SEARCH, selectedRegularText)) + assertFalse(delegate.isActionAvailable(SHARE, selectedRegularText)) + assertFalse(delegate.isActionAvailable(SEARCH_PRIVATELY, selectedRegularText)) + } + + @Test + fun `is email available when passed in and email text selected`() { + val searchAdapter = mock { + whenever(isPrivateSession()).thenReturn(false) + } + val delegate = DefaultSelectionActionDelegate( + searchAdapter, + getTestResources(), + emailTextClicked = emailClicked, + ) + + assertTrue(delegate.isActionAvailable(EMAIL, selectedEmailText)) + assertTrue(delegate.isActionAvailable(EMAIL, " $selectedEmailText ")) + assertFalse(delegate.isActionAvailable(EMAIL, selectedRegularText)) + assertFalse(delegate.isActionAvailable(EMAIL, selectedPhoneText)) + assertFalse(delegate.isActionAvailable(EMAIL, " $selectedPhoneText ")) + } + + @Test + fun `is call available when passed in and call text selected`() { + val searchAdapter = mock { + whenever(isPrivateSession()).thenReturn(false) + } + val delegate = DefaultSelectionActionDelegate( + searchAdapter, + getTestResources(), + callTextClicked = phoneClicked, + ) + + assertTrue(delegate.isActionAvailable(CALL, selectedPhoneText)) + assertFalse(delegate.isActionAvailable(CALL, selectedRegularText)) + assertFalse(delegate.isActionAvailable(CALL, selectedEmailText)) + } + + @Test + fun `are private actions available`() { + val searchAdapter = mock { + whenever(isPrivateSession()).thenReturn(true) + } + val delegate = DefaultSelectionActionDelegate( + searchAdapter, + getTestResources(), + shareClicked, + ) + + assertTrue(delegate.isActionAvailable(SEARCH_PRIVATELY, selectedRegularText)) + assertTrue(delegate.isActionAvailable(SHARE, selectedRegularText)) + assertFalse(delegate.isActionAvailable(SEARCH, selectedRegularText)) + } + + @Test + fun `when share ID is passed to perform action it should invoke the lambda`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), shareClicked) + + delegate.performAction(SHARE, "some selected text") + + assertEquals(lambdaValue, "some selected text") + } + + @Test + fun `when email ID is passed to perform action it should invoke the lambda`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), emailTextClicked = emailClicked) + + delegate.performAction(EMAIL, selectedEmailText) + + assertEquals(lambdaValue, selectedEmailText) + + delegate.performAction(EMAIL, " $selectedEmailText ") + + assertEquals(lambdaValue, selectedEmailText) + } + + @Test + fun `when call ID is passed to perform action it should invoke the lambda`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), callTextClicked = phoneClicked) + + delegate.performAction(CALL, selectedPhoneText) + + assertEquals(lambdaValue, selectedPhoneText) + + delegate.performAction(CALL, " $selectedPhoneText ") + + assertEquals(lambdaValue, selectedPhoneText) + } + + @Test + fun `when unknown ID is passed to performAction it should not perform a search`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), shareClicked) + + delegate.performAction("unrecognized string", "some selected text") + + verify(adapter, times(0)).sendSearch(anyBoolean(), anyString()) + } + + @Test + fun `when unknown ID is passed to performAction it not consume the action`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), shareClicked) + + val result = delegate.performAction("unrecognized string", "some selected text") + + assertFalse(result) + } + + @Test + fun `when search ID is passed to performAction it should perform a search`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), shareClicked) + + delegate.performAction(SEARCH, "some selected text") + + verify(adapter, times(1)).sendSearch(false, "some selected text") + } + + @Test + fun `when search ID is passed to performAction it should consume the action`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), shareClicked) + + val result = delegate.performAction(SEARCH, "some selected text") + + assertTrue(result) + } + + @Test + fun `when private search ID is passed to performAction it should perform a private normal search`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), shareClicked) + + delegate.performAction(SEARCH_PRIVATELY, "some selected text") + + verify(adapter, times(1)).sendSearch(true, "some selected text") + } + + @Test + fun `when private search ID is passed to performAction it should consume the action`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), shareClicked) + + val result = delegate.performAction(SEARCH_PRIVATELY, "some selected text") + + assertTrue(result) + } + + @Test + fun `when calling performAction check that Facts are emitted`() { + val adapter = mock() + val delegate = + DefaultSelectionActionDelegate(adapter, getTestResources(), shareClicked) + val facts = mutableListOf() + Facts.registerProcessor( + object : FactProcessor { + override fun process(fact: Fact) { + facts.add(fact) + } + }, + ) + + assertEquals(0, facts.size) + + delegate.performAction(SEARCH_PRIVATELY, "some selected text") + + assertEquals(1, facts.size) + + delegate.performAction(CALL, selectedPhoneText) + + assertEquals(2, facts.size) + } +} + +fun getTestResources() = mock { + whenever(getString(R.string.mozac_selection_context_menu_search_2)).thenReturn("Search") + whenever(getString(R.string.mozac_selection_context_menu_search_privately_2)) + .thenReturn("search privately") + whenever(getString(R.string.mozac_selection_context_menu_share)).thenReturn("share") + whenever(getString(R.string.mozac_selection_context_menu_email)).thenReturn("email") + whenever(getString(R.string.mozac_selection_context_menu_call)).thenReturn("call") +} diff --git a/mobile/android/android-components/components/feature/contextmenu/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/mobile/android/android-components/components/feature/contextmenu/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..cf1c399ea8 --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1,2 @@ +mock-maker-inline +// This allows mocking final classes (classes are final by default in Kotlin) diff --git a/mobile/android/android-components/components/feature/contextmenu/src/test/resources/robolectric.properties b/mobile/android/android-components/components/feature/contextmenu/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..932b01b9eb --- /dev/null +++ b/mobile/android/android-components/components/feature/contextmenu/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +sdk=28 -- cgit v1.2.3