diff --git a/app/src/androidTest/java/com/nextcloud/client/ActivitiesActivityIT.kt b/app/src/androidTest/java/com/nextcloud/client/ActivitiesActivityIT.kt index e6b3bef08b7d..3e403d2203c5 100644 --- a/app/src/androidTest/java/com/nextcloud/client/ActivitiesActivityIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/ActivitiesActivityIT.kt @@ -34,10 +34,6 @@ class ActivitiesActivityIT : AbstractIT() { @ScreenshotTest fun openDrawer() { launchActivity().use { scenario -> - scenario.onActivity { sut -> - sut.dismissSnackbar() - } - onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()) scenario.onActivity { sut -> @@ -54,7 +50,6 @@ class ActivitiesActivityIT : AbstractIT() { fun loading() { launchActivity().use { scenario -> scenario.onActivity { sut -> - sut.dismissSnackbar() sut.binding.emptyList.root.visibility = View.GONE sut.binding.swipeContainingList.visibility = View.GONE sut.binding.loadingContent.visibility = View.VISIBLE @@ -76,7 +71,6 @@ class ActivitiesActivityIT : AbstractIT() { scenario.onActivity { sut -> sut.showActivities(mutableListOf(), nextcloudClient, -1) sut.setProgressIndicatorState(false) - sut.dismissSnackbar() } val screenShotName = createName(testClassName + "_" + "empty", "") @@ -170,7 +164,6 @@ class ActivitiesActivityIT : AbstractIT() { scenario.onActivity { sut -> sut.showActivities(activities as List?, nextcloudClient, -1) sut.setProgressIndicatorState(false) - sut.dismissSnackbar() } val screenShotName = createName(testClassName + "_" + "showActivities", "") @@ -189,7 +182,6 @@ class ActivitiesActivityIT : AbstractIT() { scenario.onActivity { sut -> sut.showEmptyContent("Error", "Error! Please try again later!") sut.setProgressIndicatorState(false) - sut.dismissSnackbar() } val screenShotName = createName(testClassName + "_" + "error", "") diff --git a/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt b/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt new file mode 100644 index 000000000000..90980f2e5350 --- /dev/null +++ b/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt @@ -0,0 +1,142 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.utils + +import android.app.Activity +import android.app.Instrumentation +import android.content.Intent +import androidx.annotation.StringRes +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import androidx.test.core.app.launchActivity +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.Intents.intending +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.nextcloud.client.onboarding.FirstRunActivity +import com.nextcloud.test.TestActivity +import com.owncloud.android.R +import com.owncloud.android.authentication.AuthenticatorActivity +import org.hamcrest.Matchers.anyOf +import org.junit.After +import org.junit.Before +import org.junit.Test + +class SnackbarTests { + + class NormalTestFragment : Fragment() + class DialogTestFragment : DialogFragment() + class BottomSheetTestFragment : BottomSheetDialogFragment() + + @Before + fun setUp() { + Intents.init() + val cancelledResult = Instrumentation.ActivityResult(Activity.RESULT_CANCELED, Intent()) + intending( + anyOf( + hasComponent(AuthenticatorActivity::class.java.name), + hasComponent(FirstRunActivity::class.java.name) + ) + ).respondWith(cancelledResult) + } + + @After + fun tearDown() { + Intents.release() + } + + private fun assertSnackbarVisible(msg: String) { + onView(withText(msg)).check(matches(isDisplayed())) + } + + private fun assertSnackbarVisible(@StringRes msgRes: Int) { + onView(withText(msgRes)).check(matches(isDisplayed())) + } + + private fun testFragmentSnackbar(fragment: Fragment, @StringRes msgRes: Int) { + launchActivity().use { scenario -> + scenario.onActivity { sut -> + sut.addFragment(fragment) + } + scenario.onActivity { + DisplayUtils.showSnackMessage(fragment, msgRes) + } + assertSnackbarVisible(msgRes) + } + } + + @Test + fun testNormalFragmentSnackbar() { + testFragmentSnackbar(NormalTestFragment(), R.string.app_name) + } + + @Test + fun testDialogFragmentSnackbar() { + testFragmentSnackbar(DialogTestFragment(), R.string.app_name) + } + + @Test + fun testBottomSheetFragmentSnackbar() { + testFragmentSnackbar(BottomSheetTestFragment(), R.string.app_name) + } + + @Test + fun testNullFragmentSnackbarShouldNotCrash() { + DisplayUtils.showSnackMessage(null as Fragment?, R.string.app_name) + } + + @Test + fun testActivityStringResSnackbar() { + launchActivity().use { scenario -> + scenario.onActivity { sut -> + DisplayUtils.showSnackMessage(sut, R.string.app_name) + } + assertSnackbarVisible(R.string.app_name) + } + } + + @Test + fun testActivityStringSnackbar() { + launchActivity().use { scenario -> + var message = "" + scenario.onActivity { sut -> + message = sut.getString(R.string.app_name) + DisplayUtils.showSnackMessage(sut, message) + } + assertSnackbarVisible(message) + } + } + + @Test + fun testViewStringResSnackbar() { + launchActivity().use { scenario -> + scenario.onActivity { sut -> + val contentView = sut.findViewById(android.R.id.content) + DisplayUtils.showSnackMessage(contentView, R.string.app_name) + } + assertSnackbarVisible(R.string.app_name) + } + } + + @Test + fun testViewStringSnackbar() { + launchActivity().use { scenario -> + var message = "" + scenario.onActivity { sut -> + message = sut.getString(R.string.app_name) + val contentView = sut.findViewById(android.R.id.content) + DisplayUtils.showSnackMessage(contentView, message) + } + assertSnackbarVisible(message) + } + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java index 2e3a13a6dabd..5327d157a9f7 100644 --- a/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java @@ -11,7 +11,6 @@ import android.view.MenuItem; import android.view.View; -import com.google.android.material.snackbar.Snackbar; import com.nextcloud.client.network.ClientFactory; import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.common.NextcloudClient; @@ -54,7 +53,6 @@ public class ActivitiesActivity extends DrawerActivity implements ActivityListIn private long lastGiven; private boolean isLoadingActivities; private ActivitiesContract.ActionListener actionListener; - private Snackbar snackbar; @Inject ActivitiesRepository activitiesRepository; @Inject FilesRepository filesRepository; @@ -191,7 +189,7 @@ public void showActivities(List activities, NextcloudClient client, long public void showActivitiesLoadError(String error) { connectivityService.isNetworkAndServerAvailable(result -> { if (result) { - snackbar = DisplayUtils.showSnackMessage(this, error); + DisplayUtils.showSnackMessage(this, error); } else { showEmptyContent(getString(R.string.server_not_reachable), getString(R.string.server_not_reachable_content)); @@ -217,12 +215,12 @@ public void showActivityDetailUI(OCFile ocFile) { @Override public void showActivityDetailUIIsNull() { - snackbar = DisplayUtils.showSnackMessage(this, R.string.file_not_found); + DisplayUtils.showSnackMessage(this, R.string.file_not_found); } @Override public void showActivityDetailError(String error) { - snackbar = DisplayUtils.showSnackMessage(this, error); + DisplayUtils.showSnackMessage(this, error); } @Override @@ -255,12 +253,4 @@ protected void onStop() { actionListener.onStop(); } - - @VisibleForTesting - public void dismissSnackbar() { - if (snackbar != null && snackbar.isShown()) { - snackbar.dismiss(); - snackbar = null; - } - } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index c33cb35aa0de..48a1ac642937 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -186,10 +186,7 @@ private void fetchSharees() { return Unit.INSTANCE; }, () -> { showShareContainer(); - final var view = getView(); - if (view != null) { - DisplayUtils.showSnackMessage(view, R.string.error_fetching_sharees); - } + DisplayUtils.showSnackMessage(this, R.string.error_fetching_sharees); return Unit.INSTANCE; }); } @@ -414,10 +411,7 @@ public void copyInternalLink() { OwnCloudAccount account = accountManager.getCurrentOwnCloudAccount(); if (account == null) { - final var view = getView(); - if (view != null) { - DisplayUtils.showSnackMessage(view, getString(R.string.could_not_retrieve_url)); - } + DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_url); return; } @@ -581,10 +575,7 @@ public void refreshSharesFromDB() { } if (internalShareeListAdapter == null) { - final var view = getView(); - if (view != null) { - DisplayUtils.showSnackMessage(view, getString(R.string.could_not_retrieve_shares)); - } + DisplayUtils.showSnackMessage(this, R.string.could_not_retrieve_shares); return; } @@ -642,7 +633,7 @@ private void pickContactEmail() { if (intent.resolveActivity(requireContext().getPackageManager()) != null) { onContactSelectionResultLauncher.launch(intent); } else { - DisplayUtils.showSnackMessage(requireActivity(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message)); + DisplayUtils.showSnackMessage(this, R.string.file_detail_sharing_fragment_no_contact_app_message); } } @@ -665,16 +656,16 @@ private void handleContactResult(@NonNull Uri contactUri) { binding.searchView.requestFocus(); }); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address."); } } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address as no Email found."); } cursor.close(); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null."); } } @@ -737,10 +728,7 @@ public void unShare(OCShare share) { fileDataStorageManager.updateFileEntity(entity); } } else { - final var view = getView(); - if (view != null) { - DisplayUtils.showSnackMessage(view, getString(R.string.failed_update_ui)); - } + DisplayUtils.showSnackMessage(this, R.string.failed_update_ui); } } @@ -778,7 +766,7 @@ public void openShareDetailWithCustomPermissions(OCShare share) { if (isGranted) { pickContactEmail(); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.contact_no_permission); + DisplayUtils.showSnackMessage(this, R.string.contact_no_permission); } }); @@ -789,13 +777,13 @@ public void openShareDetailWithCustomPermissions(OCShare share) { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent == null) { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); return; } Uri contactUri = intent.getData(); if (contactUri == null) { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + DisplayUtils.showSnackMessage(this, R.string.email_pick_failed); return; } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt index d97dfc911c40..a8270ade7b05 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt @@ -752,14 +752,14 @@ class FileDetailsSharingProcessFragment : @Suppress("ReturnCount") private fun validateShareProcessFirst() { if (permission == OCShare.NO_PERMISSION) { - DisplayUtils.showSnackMessage(binding.root, R.string.no_share_permission_selected) + DisplayUtils.showSnackMessage(this, R.string.no_share_permission_selected) return } if (binding.shareProcessSetPasswordSwitch.isChecked && binding.shareProcessEnterPassword.text?.isBlank() == true ) { - DisplayUtils.showSnackMessage(binding.root, R.string.share_link_empty_password) + DisplayUtils.showSnackMessage(this, R.string.share_link_empty_password) return } @@ -773,7 +773,7 @@ class FileDetailsSharingProcessFragment : if (binding.shareProcessChangeNameSwitch.isChecked && binding.shareProcessChangeName.text?.isBlank() == true ) { - DisplayUtils.showSnackMessage(binding.root, R.string.label_empty) + DisplayUtils.showSnackMessage(this, R.string.label_empty) return } @@ -790,13 +790,13 @@ class FileDetailsSharingProcessFragment : @Suppress("ReturnCount") private fun createShareOrUpdateNoteShare() { if (!isAnySharePermissionChecked()) { - DisplayUtils.showSnackMessage(requireActivity(), R.string.share_option_required) + DisplayUtils.showSnackMessage(this, R.string.share_option_required) return } val noteText = binding.noteText.text.toString().trim() if (file == null && (share != null && share?.note == noteText)) { - DisplayUtils.showSnackMessage(requireActivity(), R.string.share_cannot_update_empty_note) + DisplayUtils.showSnackMessage(this, R.string.share_cannot_update_empty_note) return } @@ -807,7 +807,7 @@ class FileDetailsSharingProcessFragment : } file == null -> { - DisplayUtils.showSnackMessage(requireActivity(), R.string.file_not_found_cannot_share) + DisplayUtils.showSnackMessage(this, R.string.file_not_found_cannot_share) return } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java index 41ff60d0652e..20148300aa88 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java @@ -462,12 +462,7 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis } private void showPermissionErrorMessage() { - final var view = getView(); - if (view != null) { - DisplayUtils.showSnackMessage(view, R.string.contactlist_no_permission); - } else { - DisplayUtils.showSnackMessage(this, R.string.contactlist_no_permission); - } + DisplayUtils.showSnackMessage(this, R.string.contactlist_no_permission); } private Unit onDownloadUpdate(Transfer download) { diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java index 9be03392fcbc..09ac5d38b681 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java @@ -289,7 +289,7 @@ private void showFileActions(OCFile file) { private void onFileActionChosen(final int itemId) { if (itemId == R.id.action_send_share_file) { if (getFile().isSharedWithMe() && !getFile().canReshare()) { - DisplayUtils.showSnackMessage(getView(), R.string.resharing_is_not_allowed); + DisplayUtils.showSnackMessage(this, R.string.resharing_is_not_allowed); } else { containerActivity.getFileOperationsHelper().sendShareFile(getFile()); } diff --git a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java index 45a2a2831ee8..463532fd28b7 100644 --- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java @@ -35,6 +35,8 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -124,6 +126,7 @@ public final class DisplayUtils { public static final String MONTH_YEAR_PATTERN = "MMMM yyyy"; public static final String MONTH_PATTERN = "MMMM"; public static final String YEAR_PATTERN = "yyyy"; + private static final Handler mainLooper = new Handler(Looper.getMainLooper()); public static final int SVG_SIZE = 512; private static Map mimeType2HumanReadable; @@ -574,83 +577,101 @@ public static String getData(InputStream inputStream) { return text.toString(); } - public static Snackbar showSnackMessage(Fragment fragment, @StringRes int messageResource) { + // region snackbar + public static void showSnackMessage(Fragment fragment, @StringRes int messageResource) { if (fragment == null) { - return null; + Log_OC.e(TAG, "snackbar cannot be shown fragment is null"); + return; } final var activity = fragment.getActivity(); if (activity == null) { - return null; + Log_OC.e(TAG, "snackbar cannot be shown activity is null"); + return; } - return showSnackMessage(activity, messageResource); + showSnackMessage(activity, messageResource); } - /** - * Show a temporary message in a {@link Snackbar} bound to the content view. - * - * @param activity The {@link Activity} to which's content view the {@link Snackbar} is bound. - * @param messageResource The resource id of the string resource to use. Can be formatted text. - * @return The created {@link Snackbar} - */ - public static Snackbar showSnackMessage(Activity activity, @StringRes int messageResource) { - return showSnackMessage(activity.findViewById(android.R.id.content), messageResource); + public static void showSnackMessage(Activity activity, @StringRes int messageResource) { + if (activity == null) { + Log_OC.e(TAG, "snackbar cannot be shown activity is null"); + return; + } + + showSnackMessage(activity.findViewById(android.R.id.content), messageResource); } - /** - * Show a temporary message in a {@link Snackbar} bound to the content view. - * - * @param activity The {@link Activity} to which's content view the {@link Snackbar} is bound. - * @param message Message to show. - * @return The created {@link Snackbar} - */ - public static Snackbar showSnackMessage(Activity activity, String message) { - final Snackbar snackbar = Snackbar.make(activity.findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG); - var fab = findFABView(activity); - if (fab != null && fab.getVisibility() == View.VISIBLE) { - snackbar.setAnchorView(fab); + public static void showSnackMessage(Activity activity, @StringRes int messageResource, Object... formatArgs) { + if (activity == null) { + Log_OC.e(TAG, "snackbar cannot be shown activity is null"); + return; } + + showSnackMessage(activity, activity.findViewById(android.R.id.content), messageResource, formatArgs); + } + + public static void showSnackMessage(Context context, View view, @StringRes int messageResource, Object... formatArgs) { + if (context == null || view == null) { + Log_OC.e(TAG, "snackbar cannot be shown view is null"); + return; + } + + final var snackbar = Snackbar.make(view, String.format(context.getString(messageResource, formatArgs)), Snackbar.LENGTH_LONG); snackbar.show(); - return snackbar; } - private static View findFABView(Activity activity) { - return activity.findViewById(R.id.fab_main); + public static void showSnackMessage(Activity activity, String message) { + if (activity == null) { + Log_OC.e(TAG, "snackbar cannot be shown activity is null"); + return; + } + + activity.runOnUiThread(() -> { + final var snackbar = Snackbar.make(activity.findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG); + var fab = findFABView(activity); + if (fab != null && fab.getVisibility() == View.VISIBLE) { + snackbar.setAnchorView(fab); + } + snackbar.show(); + }); } - private static View findFABView(View view) { - return view.findViewById(R.id.fab_main); + public static void showSnackMessage(View view, @StringRes int messageResource) { + if (view == null) { + Log_OC.e(TAG, "snackbar cannot be shown view is null"); + return; + } + + mainLooper.post(() -> { + final var snackbar = Snackbar.make(view, messageResource, Snackbar.LENGTH_LONG); + var fab = findFABView(view.getRootView()); + if (fab != null && fab.getVisibility() == View.VISIBLE) { + snackbar.setAnchorView(fab); + } + snackbar.show(); + }); } - /** - * Show a temporary message in a {@link Snackbar} bound to the given view. - * - * @param view The view the {@link Snackbar} is bound to. - * @param messageResource The resource id of the string resource to use. Can be formatted text. - * @return The created {@link Snackbar} - */ - public static Snackbar showSnackMessage(View view, @StringRes int messageResource) { - final Snackbar snackbar = Snackbar.make(view, messageResource, Snackbar.LENGTH_LONG); - var fab = findFABView(view.getRootView()); - if (fab != null && fab.getVisibility() == View.VISIBLE) { - snackbar.setAnchorView(fab); + public static void showSnackMessage(View view, String message) { + if (view == null) { + Log_OC.e(TAG, "snackbar cannot be shown view is null"); + return; } - snackbar.show(); - return snackbar; + + mainLooper.post(() -> { + final Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG); + snackbar.show(); + }); } + // endregion - /** - * Show a temporary message in a {@link Snackbar} bound to the given view. - * - * @param view The view the {@link Snackbar} is bound to. - * @param message The message. - * @return The created {@link Snackbar} - */ - public static Snackbar showSnackMessage(View view, String message) { - final Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG); - snackbar.show(); - return snackbar; + private static View findFABView(Activity activity) { + return activity.findViewById(R.id.fab_main); + } + + private static View findFABView(View view) { + return view.findViewById(R.id.fab_main); } /** @@ -664,36 +685,6 @@ public static Snackbar createSnackbar(View view, @StringRes int messageResource, return Snackbar.make(view, messageResource, length); } - /** - * Show a temporary message in a {@link Snackbar} bound to the content view. - * - * @param activity The {@link Activity} to which's content view the {@link Snackbar} is bound. - * @param messageResource The resource id of the string resource to use. Can be formatted text. - * @param formatArgs The format arguments that will be used for substitution. - * @return The created {@link Snackbar} - */ - public static Snackbar showSnackMessage(Activity activity, @StringRes int messageResource, Object... formatArgs) { - return showSnackMessage(activity, activity.findViewById(android.R.id.content), messageResource, formatArgs); - } - - /** - * Show a temporary message in a {@link Snackbar} bound to the content view. - * - * @param context to load resources. - * @param view The content view the {@link Snackbar} is bound to. - * @param messageResource The resource id of the string resource to use. Can be formatted text. - * @param formatArgs The format arguments that will be used for substitution. - * @return The created {@link Snackbar} - */ - public static Snackbar showSnackMessage(Context context, View view, @StringRes int messageResource, Object... formatArgs) { - final Snackbar snackbar = Snackbar.make( - view, - String.format(context.getString(messageResource, formatArgs)), - Snackbar.LENGTH_LONG); - snackbar - .show(); - return snackbar; - } // Solution inspired by https://stackoverflow.com/questions/34936590/why-isnt-my-vector-drawable-scaling-as-expected // Copied from https://raw.githubusercontent.com/nextcloud/talk-android/8ec8606bc61878e87e3ac8ad32c8b72d4680013c/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java