Merge 2.9 release with master (#2174)

* Fix memory leak due to wikidata edit listener (#2048)

* Fix bookmark crash fix (#2047)

* Fix bookmark crash fix

* Fix check for bookmark creator

* Bug fix #2042 (#2056)

* Bug fix #2042
* Added a snack with retry when api in AA fails
* Increased connection timeouts in okhttpclient builder

* added missing string resource

* Bugfix/duplicate categories (#2080)

* Increased timeout to 60 seconds

* Bug fix #1550
* filter duplicate categories

* Fix crash because of inactive fragment UI (#2046)

* Fix crash because of inactive fragment UI

* Add java docs

* Add information icon action Fiixes #2055 2.9.0: the 'i' icon in nearby doesn't do anything (#2057)

* Localisation updates from https://translatewiki.net.

* Remove unused mediawiki api dependency (#1991)

* Categories with pipe suffix (#1873)

* Bug fix issue #1826
Changes made :
-Certain category names used to show suffixed with strings prefixed with pipe '|'. Removed everything after the pipe. As per the discussion on the thread, its safe to remove everything after the pipe, including the pipe

* review suggested changes
*Code formatting
*Extracted out the index of pipe in a variable
*Added issue link in comments

* Remove libraries section from README (#1988)

* Remove libraries section from README

* Add wiki link to "libraries used" to README

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Use alert dialog instead of popup window, for nearby information

* Revert irrelevant changes, sorry
This commit is contained in:
Vivek Maskara 2018-12-19 22:29:49 +05:30 committed by neslihanturan
parent 21edcb7cbe
commit f3b450e020
19 changed files with 91 additions and 65 deletions

View file

@ -185,6 +185,7 @@ public class AchievementsActivity extends NavigationBaseActivity {
* which then calls parseJson when results are fetched * which then calls parseJson when results are fetched
*/ */
private void setAchievements() { private void setAchievements() {
progressBar.setVisibility(View.VISIBLE);
if (checkAccount()) { if (checkAccount()) {
compositeDisposable.add(mediaWikiApi compositeDisposable.add(mediaWikiApi
.getAchievements(Objects.requireNonNull(sessionManager.getCurrentAccount()).name) .getAchievements(Objects.requireNonNull(sessionManager.getCurrentAccount()).name)
@ -195,17 +196,23 @@ public class AchievementsActivity extends NavigationBaseActivity {
if (response != null) { if (response != null) {
setUploadCount(Achievements.from(response)); setUploadCount(Achievements.from(response));
} else { } else {
onError(); showSnackBarWithRetry();
} }
}, },
t -> { t -> {
Timber.e(t, "Fetching achievements statistics failed"); Timber.e(t, "Fetching achievements statistics failed");
onError(); showSnackBarWithRetry();
} }
)); ));
} }
} }
private void showSnackBarWithRetry() {
progressBar.setVisibility(View.GONE);
ViewUtil.showDismissibleSnackBar(findViewById(android.R.id.content),
R.string.achievements_fetch_failed, R.string.retry, view -> setAchievements());
}
/** /**
* Shows a generic error toast when error occurs while loading achievements or uploads * Shows a generic error toast when error occurs while loading achievements or uploads
*/ */

View file

@ -64,7 +64,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.filter(result -> result) .filter(result -> result)
.subscribe(result -> ViewUtil.showSnackbar(findViewById(android.R.id.content), R.string.block_notification) .subscribe(result -> ViewUtil.showShortSnackbar(findViewById(android.R.id.content), R.string.block_notification)
); );
} }
} }

View file

@ -36,7 +36,7 @@ public class BookmarkPicturesController {
for (Bookmark bookmark : bookmarks) { for (Bookmark bookmark : bookmarks) {
List<Media> tmpMedias = mediaWikiApi.searchImages(bookmark.getMediaName(), 0); List<Media> tmpMedias = mediaWikiApi.searchImages(bookmark.getMediaName(), 0);
for (Media m : tmpMedias) { for (Media m : tmpMedias) {
if (m.getCreator().equals(bookmark.getMediaCreator())) { if (m.getCreator().trim().equals(bookmark.getMediaCreator().trim())) {
medias.add(m); medias.add(m);
break; break;
} }

View file

@ -129,7 +129,7 @@ public class BookmarkPicturesFragment extends DaggerFragment {
statusTextView.setVisibility(VISIBLE); statusTextView.setVisibility(VISIBLE);
statusTextView.setText(getString(R.string.no_internet)); statusTextView.setText(getString(R.string.no_internet));
} else { } else {
ViewUtil.showSnackbar(parentLayout, R.string.no_internet); ViewUtil.showShortSnackbar(parentLayout, R.string.no_internet);
} }
} }
@ -140,7 +140,7 @@ public class BookmarkPicturesFragment extends DaggerFragment {
private void handleError(Throwable throwable) { private void handleError(Throwable throwable) {
Timber.e(throwable, "Error occurred while loading images inside a category"); Timber.e(throwable, "Error occurred while loading images inside a category");
try{ try{
ViewUtil.showSnackbar(parentLayout, R.string.error_loading_images); ViewUtil.showShortSnackbar(parentLayout, R.string.error_loading_images);
initErrorView(); initErrorView();
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); e.printStackTrace();

View file

@ -124,7 +124,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
statusTextView.setVisibility(VISIBLE); statusTextView.setVisibility(VISIBLE);
statusTextView.setText(getString(R.string.no_internet)); statusTextView.setText(getString(R.string.no_internet));
} else { } else {
ViewUtil.showSnackbar(parentLayout, R.string.no_internet); ViewUtil.showShortSnackbar(parentLayout, R.string.no_internet);
} }
} }
@ -135,7 +135,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
private void handleError(Throwable throwable) { private void handleError(Throwable throwable) {
Timber.e(throwable, "Error occurred while loading images inside a category"); Timber.e(throwable, "Error occurred while loading images inside a category");
try{ try{
ViewUtil.showSnackbar(parentLayout, R.string.error_loading_images); ViewUtil.showShortSnackbar(parentLayout, R.string.error_loading_images);
initErrorView(); initErrorView();
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); e.printStackTrace();

View file

@ -135,10 +135,10 @@ public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
private void handleError(Throwable throwable) { private void handleError(Throwable throwable) {
if (!isParentCategory){ if (!isParentCategory){
Timber.e(throwable, "Error occurred while loading queried subcategories"); Timber.e(throwable, "Error occurred while loading queried subcategories");
ViewUtil.showSnackbar(categoriesRecyclerView,R.string.error_loading_categories); ViewUtil.showShortSnackbar(categoriesRecyclerView,R.string.error_loading_categories);
}else { }else {
Timber.e(throwable, "Error occurred while loading queried parentcategories"); Timber.e(throwable, "Error occurred while loading queried parentcategories");
ViewUtil.showSnackbar(categoriesRecyclerView,R.string.error_loading_categories); ViewUtil.showShortSnackbar(categoriesRecyclerView,R.string.error_loading_categories);
} }
} }
@ -161,6 +161,6 @@ public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
*/ */
private void handleNoInternet() { private void handleNoInternet() {
progressBar.setVisibility(GONE); progressBar.setVisibility(GONE);
ViewUtil.showSnackbar(categoriesRecyclerView, R.string.no_internet); ViewUtil.showShortSnackbar(categoriesRecyclerView, R.string.no_internet);
} }
} }

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions;
import android.app.AlertDialog;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -119,22 +120,19 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
// Set custom view to add nearby info icon next to text // Set custom view to add nearby info icon next to text
View nearbyTabLinearLayout = LayoutInflater.from(this).inflate(R.layout.custom_nearby_tab_layout, null); View nearbyTabLinearLayout = LayoutInflater.from(this).inflate(R.layout.custom_nearby_tab_layout, null);
View nearbyInfoPopupWindowLayout = LayoutInflater.from(this).inflate(R.layout.nearby_info_popup_layout, null);
ImageView nearbyInfo = nearbyTabLinearLayout.findViewById(R.id.nearby_info_image); ImageView nearbyInfo = nearbyTabLinearLayout.findViewById(R.id.nearby_info_image);
tabLayout.getTabAt(1).setCustomView(nearbyTabLinearLayout); tabLayout.getTabAt(1).setCustomView(nearbyTabLinearLayout);
nearbyInfo.setOnClickListener(new View.OnClickListener() { nearbyInfo.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
/*new AlertDialog.Builder(MainActivity.this) new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.title_activity_nearby) .setTitle(R.string.title_activity_nearby)
.setMessage(R.string.showcase_view_whole_nearby_activity) .setMessage(R.string.showcase_view_whole_nearby_activity)
.setCancelable(true) .setCancelable(true)
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel()) .setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
.create() .create()
.show();*/ .show();
String popupText = getResources().getString(R.string.showcase_view_whole_nearby_activity);
ViewUtil.displayPopupWindow(nearbyInfo, MainActivity.this, nearbyInfoPopupWindowLayout, popupText);
} }
}); });

View file

@ -9,6 +9,7 @@ import com.google.gson.GsonBuilder;
import java.io.File; import java.io.File;
import java.util.concurrent.TimeUnit;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -30,7 +31,9 @@ public class NetworkingModule {
@Singleton @Singleton
public OkHttpClient provideOkHttpClient(Context context) { public OkHttpClient provideOkHttpClient(Context context) {
File dir = new File(context.getCacheDir(), "okHttpCache"); File dir = new File(context.getCacheDir(), "okHttpCache");
return new OkHttpClient.Builder() return new OkHttpClient.Builder().connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.cache(new Cache(dir, OK_HTTP_CACHE_SIZE)) .cache(new Cache(dir, OK_HTTP_CACHE_SIZE))
.build(); .build();
} }

View file

@ -196,7 +196,7 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
Timber.e(throwable, "Error occurred while loading queried categories"); Timber.e(throwable, "Error occurred while loading queried categories");
try { try {
initErrorView(); initErrorView();
ViewUtil.showSnackbar(categoriesRecyclerView, R.string.error_loading_categories); ViewUtil.showShortSnackbar(categoriesRecyclerView, R.string.error_loading_categories);
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); e.printStackTrace();
} }
@ -217,6 +217,6 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
*/ */
private void handleNoInternet() { private void handleNoInternet() {
progressBar.setVisibility(GONE); progressBar.setVisibility(GONE);
ViewUtil.showSnackbar(categoriesRecyclerView, R.string.no_internet); ViewUtil.showShortSnackbar(categoriesRecyclerView, R.string.no_internet);
} }
} }

View file

@ -206,7 +206,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
private void handleError(Throwable throwable) { private void handleError(Throwable throwable) {
Timber.e(throwable, "Error occurred while loading queried images"); Timber.e(throwable, "Error occurred while loading queried images");
try { try {
ViewUtil.showSnackbar(imagesRecyclerView, R.string.error_loading_images); ViewUtil.showShortSnackbar(imagesRecyclerView, R.string.error_loading_images);
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); e.printStackTrace();
} }
@ -226,7 +226,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
*/ */
private void handleNoInternet() { private void handleNoInternet() {
progressBar.setVisibility(GONE); progressBar.setVisibility(GONE);
ViewUtil.showSnackbar(imagesRecyclerView, R.string.no_internet); ViewUtil.showShortSnackbar(imagesRecyclerView, R.string.no_internet);
} }
/** /**

View file

@ -261,7 +261,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
menu.clear(); // see http://stackoverflow.com/a/8495697/17865 menu.clear(); // see http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_image_detail, menu); inflater.inflate(R.menu.fragment_image_detail, menu);
if (pager != null) { if (pager != null) {
MediaDetailProvider provider = (MediaDetailProvider) getParentFragment(); MediaDetailProvider provider = getMediaDetailProvider();
if(provider == null) { if(provider == null) {
return; return;
} }

View file

@ -355,6 +355,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
@Override @Override
@NonNull @NonNull
public Observable<String> searchCategories(String filterValue, int searchCatsLimit) { public Observable<String> searchCategories(String filterValue, int searchCatsLimit) {
List<String> categories = new ArrayList<>();
return Single.fromCallable(() -> { return Single.fromCallable(() -> {
List<CustomApiResult> categoryNodes = null; List<CustomApiResult> categoryNodes = null;
try { try {
@ -375,12 +376,13 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return new ArrayList<String>(); return new ArrayList<String>();
} }
List<String> categories = new ArrayList<>();
for (CustomApiResult categoryNode : categoryNodes) { for (CustomApiResult categoryNode : categoryNodes) {
String cat = categoryNode.getDocument().getTextContent(); String cat = categoryNode.getDocument().getTextContent();
String catString = cat.replace("Category:", ""); String catString = cat.replace("Category:", "");
if (!categories.contains(catString)) {
categories.add(catString); categories.add(catString);
} }
}
return categories; return categories;
}).flatMapObservable(Observable::fromIterable); }).flatMapObservable(Observable::fromIterable);

View file

@ -16,7 +16,6 @@ import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@ -38,6 +37,7 @@ import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener; import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.utils.FragmentUtils;
import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.UriSerializer; import fr.free.nrw.commons.utils.UriSerializer;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
@ -116,7 +116,6 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
resumeFragment();*/ resumeFragment();*/
bundle = new Bundle(); bundle = new Bundle();
initBottomSheetBehaviour(); initBottomSheetBehaviour();
wikidataEditListener.setAuthenticationStateListener(this);
this.view = view; this.view = view;
return view; return view;
} }
@ -387,7 +386,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
String gsonBoundaryCoordinates = gson.toJson(boundaryCoordinates); String gsonBoundaryCoordinates = gson.toJson(boundaryCoordinates);
if (placeList.size() == 0) { if (placeList.size() == 0) {
ViewUtil.showSnackbar(view.findViewById(R.id.container), R.string.no_nearby); ViewUtil.showShortSnackbar(view.findViewById(R.id.container), R.string.no_nearby);
} }
bundle.putString("PlaceList", gsonPlaceList); bundle.putString("PlaceList", gsonPlaceList);
@ -673,14 +672,17 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
} }
private void addNetworkBroadcastReceiver() { private void addNetworkBroadcastReceiver() {
if (!FragmentUtils.isFragmentUIActive(this)) {
return;
}
IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION); IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION);
snackbar = Snackbar.make(transparentView , R.string.no_internet, Snackbar.LENGTH_INDEFINITE); snackbar = Snackbar.make(transparentView, R.string.no_internet, Snackbar.LENGTH_INDEFINITE);
broadcastReceiver = new BroadcastReceiver() { broadcastReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (snackbar != null) { if (snackbar != null && getActivity() != null) {
if (NetworkUtils.isInternetConnectionEstablished(getActivity())) { if (NetworkUtils.isInternetConnectionEstablished(getActivity())) {
refreshView(LOCATION_SIGNIFICANTLY_CHANGED); refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
snackbar.dismiss(); snackbar.dismiss();
@ -691,6 +693,10 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
} }
}; };
if (getActivity() == null) {
return;
}
getActivity().registerReceiver(broadcastReceiver, intentFilter); getActivity().registerReceiver(broadcastReceiver, intentFilter);
} }
@ -723,6 +729,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
wikidataEditListener.setAuthenticationStateListener(this);
} }
@Override @Override
@ -731,6 +738,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
if (placesDisposable != null) { if (placesDisposable != null) {
placesDisposable.dispose(); placesDisposable.dispose();
} }
wikidataEditListener.setAuthenticationStateListener(null);
if (placesDisposableCustom != null) { if (placesDisposableCustom != null) {
placesDisposableCustom.dispose(); placesDisposableCustom.dispose();
} }
@ -741,6 +749,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
super.onDetach(); super.onDetach();
snackbar = null; snackbar = null;
broadcastReceiver = null; broadcastReceiver = null;
wikidataEditListener.setAuthenticationStateListener(null);
} }
@Override @Override

View file

@ -106,7 +106,7 @@ public class NotificationActivity extends NavigationBaseActivity {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
}, throwable -> { }, throwable -> {
Timber.e(throwable, "Error occurred while loading notifications"); Timber.e(throwable, "Error occurred while loading notifications");
ViewUtil.showSnackbar(relativeLayout, R.string.error_notifications); ViewUtil.showShortSnackbar(relativeLayout, R.string.error_notifications);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
}); });
} else { } else {
@ -123,7 +123,7 @@ public class NotificationActivity extends NavigationBaseActivity {
private void setAdapter(List<Notification> notificationList) { private void setAdapter(List<Notification> notificationList) {
if (notificationList == null || notificationList.isEmpty()) { if (notificationList == null || notificationList.isEmpty()) {
ViewUtil.showSnackbar(relativeLayout, R.string.no_notifications); ViewUtil.showShortSnackbar(relativeLayout, R.string.no_notifications);
return; return;
} }
notificationAdapterFactory = new NotificationAdapterFactory(notification -> { notificationAdapterFactory = new NotificationAdapterFactory(notification -> {

View file

@ -28,4 +28,13 @@ public class FragmentUtils {
} }
return false; return false;
} }
/**
* Utility function to check whether the fragment UI is still active or not
* @param fragment
* @return
*/
public static boolean isFragmentUIActive(Fragment fragment) {
return fragment.getActivity() != null && fragment.isAdded() && !fragment.isDetached() && !fragment.isRemoving();
}
} }

View file

@ -11,6 +11,9 @@ public class NetworkUtils {
ConnectivityManager cm = ConnectivityManager cm =
(ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
return false;
}
NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && return activeNetwork != null &&
activeNetwork.isConnectedOrConnecting(); activeNetwork.isConnectedOrConnecting();

View file

@ -17,7 +17,12 @@ public class ViewUtil {
public static final String SHOWCASE_VIEW_ID_2 = "SHOWCASE_VIEW_ID_2"; public static final String SHOWCASE_VIEW_ID_2 = "SHOWCASE_VIEW_ID_2";
public static final String SHOWCASE_VIEW_ID_3 = "SHOWCASE_VIEW_ID_3"; public static final String SHOWCASE_VIEW_ID_3 = "SHOWCASE_VIEW_ID_3";
public static void showSnackbar(View view, int messageResourceId) { /**
* Utility function to show short snack bar
* @param view
* @param messageResourceId
*/
public static void showShortSnackbar(View view, int messageResourceId) {
if (view.getContext() == null) { if (view.getContext() == null) {
return; return;
} }
@ -76,15 +81,23 @@ public class ViewUtil {
} }
} }
public static void displayPopupWindow(View anchorView, Context context, View popupWindowLayout, String text) { /**
* A snack bar which has an action button which on click dismisses the snackbar and invokes the
PopupWindow popup = new PopupWindow(context); * listener passed
popup.setContentView(popupWindowLayout); */
// Closes the popup window when touch outside of it - when looses focus public static void showDismissibleSnackBar(View view, int messageResourceId,
popup.setOutsideTouchable(true); int actionButtonResourceId, View.OnClickListener onClickListener) {
popup.setFocusable(true); if (view.getContext() == null) {
// Show anchored to button return;
popup.showAsDropDown(anchorView); }
ExecutorUtils.uiExecutor().execute(() -> {
Snackbar snackbar = Snackbar.make(view, view.getContext().getString(messageResourceId),
Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(view.getContext().getString(actionButtonResourceId), v -> {
snackbar.dismiss();
onClickListener.onClick(v);
});
snackbar.show();
});
} }
} }

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/mainTabBackground"
>
<TextView
android:id="@+id/info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="16dp"
android:layout_marginHorizontal="8dp"
android:gravity="center"
android:textColor="@color/white"
android:text="@string/showcase_view_whole_nearby_activity" />
</LinearLayout>

View file

@ -437,6 +437,7 @@ Upload your first media by touching the camera or gallery icon above.</string>
<string name="never_ask_again">Never ask this again</string> <string name="never_ask_again">Never ask this again</string>
<string name="display_location_permission_title">Display location permission</string> <string name="display_location_permission_title">Display location permission</string>
<string name="display_location_permission_explanation">Ask for location permission when needed for nearby notification card view feature.</string> <string name="display_location_permission_explanation">Ask for location permission when needed for nearby notification card view feature.</string>
<string name="achievements_fetch_failed">Something went wrong, We could not fetch your achievements</string>
<string name="ends_on">Ends on:</string> <string name="ends_on">Ends on:</string>
<string name="display_campaigns">Display campaigns</string> <string name="display_campaigns">Display campaigns</string>
<string name="display_campaigns_explanation">Tap here to see the ongoing campaigns</string> <string name="display_campaigns_explanation">Tap here to see the ongoing campaigns</string>