From 1b62ac4d2dd9710d06f09618ee7eafaf26601e10 Mon Sep 17 00:00:00 2001 From: Vanshika Arora <34261945+vanshikaarora@users.noreply.github.com> Date: Sat, 2 Feb 2019 19:21:58 +0530 Subject: [PATCH] Code to retrive unknown notification and UI (#2340) * request change, changed notification icon * Completed task 1 of the work * commit changes * commit changes * updated notification class * before notification id * gradle reverted * Minor changes to mark notifications as read * commit changes * delete on swipe * notification count * sipe to delete * changes * worked on changes requested * commit changes * Fix notification count * reviewed changes * round icon, swipe with icon * Fix pending NPE issues with notifications * final commit * graddle changes * removed changes for testing --- app/build.gradle | 3 + .../contributions/ContributionsFragment.java | 14 +-- .../commons/contributions/MainActivity.java | 79 ++++++------ .../commons/di/CommonsApplicationModule.java | 11 -- .../mwapi/ApacheHttpClientMediaWikiApi.java | 21 +++- .../free/nrw/commons/mwapi/MediaWikiApi.java | 3 + .../commons/notification/Notification.java | 18 ++- .../notification/NotificationActivity.java | 114 +++++++++++++----- .../notification/NotificationController.java | 3 + .../notification/NotificationRenderer.java | 69 +++++++++-- .../notification/NotificationUtils.java | 8 +- .../UnreadNotificationsCheckAsync.java | 81 ------------- .../res/drawable/ic_delete_black_24dp.xml | 5 + .../ic_notifications_off_black_24dp.xml | 5 + .../main/res/drawable/notification_badge.xml | 10 ++ .../main/res/layout/activity_notification.xml | 40 ++++++ app/src/main/res/layout/item_notification.xml | 34 +++++- app/src/main/res/layout/notification_icon.xml | 38 ++++++ ...ontribution_activity_notification_menu.xml | 10 +- app/src/main/res/values/colors.xml | 4 +- app/src/main/res/values/strings.xml | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 22 files changed, 381 insertions(+), 195 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/notification/UnreadNotificationsCheckAsync.java create mode 100644 app/src/main/res/drawable/ic_delete_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_notifications_off_black_24dp.xml create mode 100644 app/src/main/res/drawable/notification_badge.xml create mode 100644 app/src/main/res/layout/notification_icon.xml diff --git a/app/build.gradle b/app/build.gradle index d85b1976b..eb2c64b62 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -88,6 +88,9 @@ dependencies { implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION" implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION" implementation 'com.android.support.constraint:constraint-layout:1.1.3' + //swipe_layout + implementation 'com.daimajia.swipelayout:library:1.2.0@aar' + implementation 'com.nineoldandroids:library:2.4.0' } android { diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index d9a03e89d..e4d16cf39 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -31,7 +31,6 @@ import android.widget.CheckBox; import android.widget.Toast; import java.util.ArrayList; -import java.util.concurrent.CountDownLatch; import javax.inject.Inject; import javax.inject.Named; @@ -56,7 +55,6 @@ import fr.free.nrw.commons.nearby.NearbyController; import fr.free.nrw.commons.nearby.NearbyNotificationCardView; import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.notification.NotificationController; -import fr.free.nrw.commons.notification.UnreadNotificationsCheckAsync; import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.upload.UploadService; import fr.free.nrw.commons.utils.ConfigUtils; @@ -87,8 +85,7 @@ public class ContributionsFragment @Inject @Named("default_preferences") BasicKvStore defaultKvStore; @Inject ContributionDao contributionDao; @Inject MediaWikiApi mediaWikiApi; - @Inject NotificationController notificationController; - @Inject NearbyController nearbyController; + @Inject NearbyController nearbyController; private ArrayList observersWaitingForLoad = new ArrayList<>(); private UploadService uploadService; @@ -213,7 +210,6 @@ public class ContributionsFragment if (((MainActivity)getActivity()).isAuthCookieAcquired && !isFragmentAttachedBefore) { onAuthCookieAcquired(((MainActivity)getActivity()).uploadServiceIntent); isFragmentAttachedBefore = true; - new UnreadNotificationsCheckAsync((MainActivity) getActivity(), notificationController).execute(); } } @@ -478,14 +474,6 @@ public class ContributionsFragment displayUploadCount(betaUploadCount); } - /** - * Updates notification indicator on toolbar to indicate there are unread notifications - * @param isThereUnreadNotifications true if user checked notifications before last notification date - */ - public void updateNotificationsNotification(boolean isThereUnreadNotifications) { - ((MainActivity)getActivity()).updateNotificationIcon(isThereUnreadNotifications); - } - @Override public void onPause() { super.onPause(); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java index cf9c286a6..07f87655d 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.contributions; +import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.Intent; import android.content.pm.PackageManager; @@ -8,7 +9,6 @@ import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.content.ContextCompat; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.Menu; @@ -16,6 +16,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; +import android.widget.TextView; import com.esafirm.imagepicker.features.ImagePicker; import com.esafirm.imagepicker.model.Image; @@ -35,10 +36,15 @@ import fr.free.nrw.commons.kvstore.BasicKvStore; import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.nearby.NearbyFragment; import fr.free.nrw.commons.nearby.NearbyNotificationCardView; +import fr.free.nrw.commons.notification.Notification; import fr.free.nrw.commons.notification.NotificationActivity; +import fr.free.nrw.commons.notification.NotificationController; import fr.free.nrw.commons.upload.UploadService; import fr.free.nrw.commons.utils.ImageUtils; import fr.free.nrw.commons.utils.IntentUtils; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; import timber.log.Timber; import static android.content.ContentResolver.requestSync; @@ -58,6 +64,8 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag @Inject @Named("default_preferences") public BasicKvStore defaultKvStore; + @Inject + NotificationController notificationController; public Intent uploadServiceIntent; @@ -69,10 +77,12 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag public boolean isContributionsFragmentVisible = true; // False means nearby fragment is visible private Menu menu; - private boolean isThereUnreadNotifications = false; private boolean onOrientationChanged = false; + private MenuItem notificationsMenuItem; + private TextView notificationCount; + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_contributions); @@ -82,6 +92,7 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag initDrawer(); setTitle(getString(R.string.navigation_item_home)); // Should I create a new string variable with another name instead? + if (savedInstanceState != null ) { onOrientationChanged = true; // Will be used in nearby fragment to determine significant update of map @@ -126,13 +137,11 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag tabLayout.getTabAt(1).setCustomView(nearbyTabLinearLayout); nearbyInfo.setOnClickListener(view -> - new AlertDialog.Builder(MainActivity.this) - .setTitle(R.string.title_activity_nearby) - .setMessage(R.string.showcase_view_whole_nearby_activity) - .setCancelable(true) - .setPositiveButton(android.R.string.ok, (dialog, id) -> dialog.cancel()) - .create() - .show() + new AlertDialog.Builder(MainActivity.this).setTitle(R.string.title_activity_nearby).setMessage(R.string.showcase_view_whole_nearby_activity) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, (dialog, id) -> dialog.cancel()) + .create() + .show() ); if (uploadServiceIntent != null) { @@ -278,20 +287,35 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.contribution_activity_notification_menu, menu); - if (!isThereUnreadNotifications) { - // TODO: used vectors are not compatible with API 19 and below, change them - menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art)); - } else { - menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art_dot)); - } - + notificationsMenuItem = menu.findItem(R.id.notifications); + final View notification = notificationsMenuItem.getActionView(); + notificationCount = notification.findViewById(R.id.notification_count_badge); + notification.setOnClickListener(view -> NotificationActivity.startYourself(MainActivity.this)); this.menu = menu; - updateMenuItem(); - + setNotificationCount(); return true; } + @SuppressLint("CheckResult") + private void setNotificationCount() { + Observable.fromCallable(() -> notificationController.getNotifications()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::initNotificationViews, + throwable -> Timber.e(throwable, "Error occurred while loading notifications")); + } + + private void initNotificationViews(List notificationList) { + Timber.d("Number of notifications is %d", notificationList.size()); + if (notificationList.isEmpty()) { + notificationCount.setVisibility(View.GONE); + } else { + notificationCount.setVisibility(View.VISIBLE); + notificationCount.setText(String.valueOf(notificationList.size())); + } + } + /** * Responsible with displaying required menu items according to displayed fragment. * Notifications icon when contributions list is visible, list sheet icon when nearby is visible @@ -319,7 +343,7 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag // Starts notification activity on click to notification icon NotificationActivity.startYourself(this); return true; - case R.id.list_sheet: + case R.id.list_sheet:NotificationActivity.startYourself(this); if (contributionsActivityPagerAdapter.getItem(1) != null) { ((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).listOptionMenuIteClicked(); } @@ -335,21 +359,6 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT); } - /** - * Update notification icon if there is an unread notification - * @param isThereUnreadNotifications true if user didn't visit notifications activity since - * latest notification came to account - */ - public void updateNotificationIcon(boolean isThereUnreadNotifications) { - if (!isThereUnreadNotifications) { - this.isThereUnreadNotifications = false; - menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art)); - } else { - this.isThereUnreadNotifications = true; - menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art_dot)); - } - } - public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter { FragmentManager fragmentManager; private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details @@ -471,7 +480,7 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag if (!isContributionsFragmentVisible) { viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION); - // TODO: If contrib fragment is visible and location permission is not given, display permission request button + // TODO: If contrib fragment is visible and location permission is not given, display permission request button } else { } diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java index b21aa2124..37d8538e6 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java @@ -164,17 +164,6 @@ public class CommonsApplicationModule { return new JsonKvStore(context, "direct_nearby_upload_prefs", gson); } - /** - * Is used to determine when user is viewed notifications activity last - * @param context - * @return date of lastReadNotificationDate - */ - @Provides - @Named("last_read_notification_date") - public BasicKvStore providesLastReadNotificationDateKvStore(Context context) { - return new BasicKvStore(context, "last_read_notification_date"); - } - @Provides public UploadController providesUploadController(SessionManager sessionManager, @Named("default_preferences") BasicKvStore kvStore, diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java index b454c2a06..a4c355d0d 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java @@ -54,6 +54,7 @@ import fr.free.nrw.commons.notification.Notification; import fr.free.nrw.commons.notification.NotificationUtils; import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.DateUtils; +import fr.free.nrw.commons.utils.StringUtils; import fr.free.nrw.commons.utils.ViewUtil; import in.yuvi.http.fluent.Http; import io.reactivex.Observable; @@ -82,7 +83,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { private Gson gson; private final OkHttpClient okHttpClient; private final String WIKIMEDIA_CAMPAIGNS_BASE_URL = - "https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json"; + "https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json"; private final String ERROR_CODE_BAD_TOKEN = "badtoken"; @@ -587,6 +588,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { .param("meta", "notifications") .param("notformat", "model") .param("notwikis", "wikidatawiki|commonswiki|enwiki") + .param("notfilter","!read") .get() .getNode("/api/query/notifications/list"); } catch (IOException e) { @@ -599,11 +601,26 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { || notificationNode.getDocument().getChildNodes().getLength() == 0) { return new ArrayList<>(); } - NodeList childNodes = notificationNode.getDocument().getChildNodes(); return NotificationUtils.getNotificationsFromList(context, childNodes); } + @Override + public boolean markNotificationAsRead(Notification notification) throws IOException { + Timber.d("Trying to mark notification as read: %s", notification.toString()); + String result = api.action("echomarkread") + .param("token", getEditToken()) + .param("list", notification.notificationId) + .post() + .getString("/api/query/echomarkread/@result"); + + if (StringUtils.isNullOrWhiteSpace(result)) { + return false; + } + + return result.equals("success"); + } + /** * The method takes categoryName as input and returns a List of Subcategories * It uses the generator query API to get the subcategories in a category, 500 at a time. diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java index 46d71dc26..1d4566c40 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java @@ -84,6 +84,9 @@ public interface MediaWikiApi { @NonNull List getNotifications() throws IOException; + @NonNull + boolean markNotificationAsRead(Notification notification) throws IOException; + @NonNull Observable searchTitles(String title, int searchCatsLimit); diff --git a/app/src/main/java/fr/free/nrw/commons/notification/Notification.java b/app/src/main/java/fr/free/nrw/commons/notification/Notification.java index 0116d024c..fec22f34d 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/Notification.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/Notification.java @@ -12,8 +12,9 @@ public class Notification { public String link; public String iconUrl; public String dateWithYear; + public String notificationId; - public Notification(NotificationType notificationType, String notificationText, String date, String description, String link, String iconUrl, String dateWithYear) { + public Notification(NotificationType notificationType, String notificationText, String date, String description, String link, String iconUrl, String dateWithYear, String notificationId) { this.notificationType = notificationType; this.notificationText = notificationText; this.date = date; @@ -21,5 +22,20 @@ public class Notification { this.link = link; this.iconUrl = iconUrl; this.dateWithYear = dateWithYear; + this.notificationId=notificationId; + } + + @Override + public String toString() { + return "Notification" + + "notificationType='" + notificationType + '\'' + + ", notificationText='" + notificationText + '\'' + + ", date='" + date + '\'' + + ", description='" + description + '\'' + + ", link='" + link + '\'' + + ", iconUrl='" + iconUrl + '\'' + + ", dateWithYear=" + dateWithYear + + ", notificationId='" + notificationId + '\'' + + '}'; } } diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java index 17390e5fb..9f327abe1 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java @@ -1,11 +1,11 @@ package fr.free.nrw.commons.notification; import android.annotation.SuppressLint; -import android.app.FragmentManager; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.support.constraint.ConstraintLayout; import android.support.design.widget.Snackbar; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; @@ -13,23 +13,19 @@ import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.ProgressBar; import android.widget.RelativeLayout; +import android.widget.Toast; import com.pedrogomez.renderers.RVRendererAdapter; import java.util.Collections; -import java.util.Date; import java.util.List; import javax.inject.Inject; -import javax.inject.Named; import butterknife.BindView; import butterknife.ButterKnife; import fr.free.nrw.commons.R; import fr.free.nrw.commons.Utils; -import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.kvstore.BasicKvStore; -import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.ViewUtil; @@ -44,17 +40,23 @@ import timber.log.Timber; public class NotificationActivity extends NavigationBaseActivity { NotificationAdapterFactory notificationAdapterFactory; - - @BindView(R.id.listView) RecyclerView recyclerView; - @BindView(R.id.progressBar) ProgressBar progressBar; - @BindView(R.id.container) RelativeLayout relativeLayout; - - @Inject NotificationController controller; - @Inject MediaWikiApi mediaWikiApi; - @Inject @Named("last_read_notification_date") BasicKvStore kvStore; + @BindView(R.id.listView) + RecyclerView recyclerView; + @BindView(R.id.progressBar) + ProgressBar progressBar; + @BindView(R.id.container) + RelativeLayout relativeLayout; + @BindView(R.id.no_notification_background) + ConstraintLayout no_notification; + /* @BindView(R.id.swipe_bg) + TextView swipe_bg;*/ + @Inject + NotificationController controller; private static final String TAG_NOTIFICATION_WORKER_FRAGMENT = "NotificationWorkerFragment"; private NotificationWorkerFragment mNotificationWorkerFragment; + private RVRendererAdapter adapter; + private List notificationList; @Override protected void onCreate(Bundle savedInstanceState) { @@ -62,11 +64,46 @@ public class NotificationActivity extends NavigationBaseActivity { setContentView(R.layout.activity_notification); ButterKnife.bind(this); mNotificationWorkerFragment = (NotificationWorkerFragment) getFragmentManager() - .findFragmentByTag(TAG_NOTIFICATION_WORKER_FRAGMENT); + .findFragmentByTag(TAG_NOTIFICATION_WORKER_FRAGMENT); initListView(); initDrawer(); } + @SuppressLint("CheckResult") + public void removeNotification(Notification notification) { + Observable.fromCallable(() -> controller.markAsRead(notification)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result){ + notificationList.remove(notification); + setAdapter(notificationList); + adapter.notifyDataSetChanged(); + Snackbar snackbar = Snackbar + .make(relativeLayout,"Notification marked as read", Snackbar.LENGTH_LONG); + + snackbar.show(); + if (notificationList.size()==0){ + relativeLayout.setVisibility(View.GONE); + no_notification.setVisibility(View.VISIBLE); + } + } + else { + adapter.notifyDataSetChanged(); + setAdapter(notificationList); + Toast.makeText(NotificationActivity.this, "There was some error!", Toast.LENGTH_SHORT).show(); + } + }, throwable -> { + + Timber.e(throwable, "Error occurred while loading notifications"); + throwable.printStackTrace(); + ViewUtil.showShortSnackbar(relativeLayout, R.string.error_notifications); + progressBar.setVisibility(View.GONE); + }); + } + + + private void initListView() { recyclerView.setLayoutManager(new LinearLayoutManager(this)); DividerItemDecoration itemDecor = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL); @@ -77,9 +114,9 @@ public class NotificationActivity extends NavigationBaseActivity { private void refresh() { if (!NetworkUtils.isInternetConnectionEstablished(this)) { progressBar.setVisibility(View.GONE); - Snackbar.make(relativeLayout , R.string.no_internet, Snackbar.LENGTH_INDEFINITE) + Snackbar.make(relativeLayout, R.string.no_internet, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.retry, view -> refresh()).show(); - }else { + } else { progressBar.setVisibility(View.VISIBLE); addNotifications(); } @@ -88,13 +125,7 @@ public class NotificationActivity extends NavigationBaseActivity { @SuppressLint("CheckResult") private void addNotifications() { Timber.d("Add notifications"); - - // Store when add notification is called last - long currentDate = new Date(System.currentTimeMillis()).getTime(); - kvStore.putLong("last_read_notification_date", currentDate); - Timber.d("Set last notification read date to current date:"+ currentDate); - - if(mNotificationWorkerFragment == null){ + if (mNotificationWorkerFragment == null) { Observable.fromCallable(() -> { progressBar.setVisibility(View.VISIBLE); return controller.getNotifications(); @@ -104,7 +135,13 @@ public class NotificationActivity extends NavigationBaseActivity { .subscribe(notificationList -> { Collections.reverse(notificationList); Timber.d("Number of notifications is %d", notificationList.size()); - setAdapter(notificationList); + this.notificationList = notificationList; + if (notificationList.size()==0){ + relativeLayout.setVisibility(View.GONE); + no_notification.setVisibility(View.VISIBLE); + } else { + setAdapter(notificationList); + } progressBar.setVisibility(View.GONE); }, throwable -> { Timber.e(throwable, "Error occurred while loading notifications"); @@ -112,7 +149,8 @@ public class NotificationActivity extends NavigationBaseActivity { progressBar.setVisibility(View.GONE); }); } else { - setAdapter(mNotificationWorkerFragment.getNotificationList()); + notificationList = mNotificationWorkerFragment.getNotificationList(); + setAdapter(notificationList); } } @@ -126,13 +164,28 @@ public class NotificationActivity extends NavigationBaseActivity { private void setAdapter(List notificationList) { if (notificationList == null || notificationList.isEmpty()) { ViewUtil.showShortSnackbar(relativeLayout, R.string.no_notifications); + /*progressBar.setVisibility(View.GONE); + recyclerView.setVisibility(View.GONE);*/ + relativeLayout.setVisibility(View.GONE); + no_notification.setVisibility(View.VISIBLE); return; } - notificationAdapterFactory = new NotificationAdapterFactory(notification -> { - Timber.d("Notification clicked %s", notification.link); - handleUrl(notification.link); + notificationAdapterFactory = new NotificationAdapterFactory(new NotificationRenderer.NotificationClicked() { + @Override + public void notificationClicked(Notification notification) { + Timber.d("Notification clicked %s", notification.link); + handleUrl(notification.link); + } + + @Override + public void markNotificationAsRead(Notification notification) { + Timber.d("Notification to mark as read %s", notification.notificationId); + removeNotification(notification); + } }); - RVRendererAdapter adapter = notificationAdapterFactory.create(notificationList); + adapter = notificationAdapterFactory.create(notificationList); + relativeLayout.setVisibility(View.VISIBLE); + no_notification.setVisibility(View.GONE); recyclerView.setAdapter(adapter); } @@ -141,5 +194,4 @@ public class NotificationActivity extends NavigationBaseActivity { intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); context.startActivity(intent); } - } diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationController.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationController.java index b22bafbb5..db5360612 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationController.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationController.java @@ -36,4 +36,7 @@ public class NotificationController { } return new ArrayList<>(); } + public boolean markAsRead(Notification notification) throws IOException{ + return mediaWikiApi.markNotificationAsRead(notification); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java index 22bebb37a..a3be82191 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java @@ -1,36 +1,55 @@ package fr.free.nrw.commons.notification; +import android.graphics.Color; import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; +import com.nineoldandroids.view.ViewHelper; import com.pedrogomez.renderers.Renderer; - +import com.daimajia.swipe.SwipeLayout; import butterknife.BindView; import butterknife.ButterKnife; +import butterknife.OnClick; import fr.free.nrw.commons.R; +import timber.log.Timber; /** * Created by root on 19.12.2017. */ public class NotificationRenderer extends Renderer { - @BindView(R.id.title) TextView title; - @BindView(R.id.time) TextView time; - @BindView(R.id.icon) ImageView icon; + @BindView(R.id.title) + TextView title; + @BindView(R.id.time) + TextView time; + @BindView(R.id.icon) + ImageView icon; + @BindView(R.id.swipeLayout) + SwipeLayout swipeLayout; + @BindView(R.id.bottom) + LinearLayout bottomLayout; + private NotificationClicked listener; NotificationRenderer(NotificationClicked listener) { this.listener = listener; } - + @OnClick(R.id.bottom) + void onBottomLayoutClicked(){ + Notification notification = getContent(); + Timber.d("NotificationID: %s", notification.notificationId); + listener.markNotificationAsRead(notification); + } @Override - protected void setUpView(View view) { } + protected void setUpView(View rootView) { + } @Override protected void hookListeners(View rootView) { rootView.setOnClickListener(v -> listener.notificationClicked(getContent())); @@ -40,9 +59,39 @@ public class NotificationRenderer extends Renderer { protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) { View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false); ButterKnife.bind(this, inflatedView); + + swipeLayout.addDrag(SwipeLayout.DragEdge.Top, bottomLayout); + swipeLayout.addRevealListener(R.id.bottom_wrapper_child1, (child, edge, fraction, distance) -> { + View star = child.findViewById(R.id.star); + float d = child.getHeight() / 2 - star.getHeight() / 2; + ViewHelper.setTranslationY(star, d * fraction); + ViewHelper.setScaleX(star, fraction + 0.6f); + ViewHelper.setScaleY(star, fraction + 0.6f); + int c = (Integer) evaluate(fraction, Color.parseColor("#dddddd"), Color.parseColor("#90960a0a")); + child.setBackgroundColor(c); + }); return inflatedView; } + public Object evaluate(float fraction, Object startValue, Object endValue) { + int startInt = (Integer) startValue; + int startA = (startInt >> 24) & 0xff; + int startR = (startInt >> 16) & 0xff; + int startG = (startInt >> 8) & 0xff; + int startB = startInt & 0xff; + + int endInt = (Integer) endValue; + int endA = (endInt >> 24) & 0xff; + int endR = (endInt >> 16) & 0xff; + int endG = (endInt >> 8) & 0xff; + int endB = endInt & 0xff; + + return (int) ((startA + (int) (fraction * (endA - startA))) << 24) | + (int) ((startR + (int) (fraction * (endR - startR))) << 16) | + (int) ((startG + (int) (fraction * (endG - startG))) << 8) | + (int) ((startB + (int) (fraction * (endB - startB)))); + } + @Override public void render() { Notification notification = getContent(); @@ -53,20 +102,22 @@ public class NotificationRenderer extends Renderer { /** * Cleans up the notification text and sets it as the title * Clean up is required to fix escaped HTML string and extra white spaces at the beginning of the notification + * * @param notificationText */ private void setTitle(String notificationText) { notificationText = notificationText.trim().replaceAll("(^\\s*)|(\\s*$)", ""); notificationText = Html.fromHtml(notificationText).toString(); - if(notificationText.length()>280){ - notificationText = notificationText.substring(0,279); + if (notificationText.length() > 280) { + notificationText = notificationText.substring(0, 279); notificationText = notificationText.concat("..."); } notificationText = notificationText.concat(" "); title.setText(notificationText); } - public interface NotificationClicked{ + public interface NotificationClicked { void notificationClicked(Notification notification); + void markNotificationAsRead(Notification notification); } } diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java index 575e1a4dc..4920eb143 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java @@ -74,6 +74,11 @@ public class NotificationUtils { return NotificationType.handledValueOf(type); } + public static String getNotificationId(Node document) { + Element element = (Element) document; + return element.getAttribute("id"); + } + public static List getNotificationsFromBundle(Context context, Node document) { Element bundledNotifications = getBundledNotifications(document); NodeList childNodes = bundledNotifications.getChildNodes(); @@ -154,7 +159,8 @@ public class NotificationUtils { notificationText = getWelcomeMessage(context, document); break; } - return new Notification(type, notificationText, getTimestamp(document), description, link, iconUrl, getTimestampWithYear(document)); + return new Notification(type, notificationText, getTimestamp(document), description, link, iconUrl, getTimestampWithYear(document), + getNotificationId(document)); } private static String getNotificationText(Node document) { diff --git a/app/src/main/java/fr/free/nrw/commons/notification/UnreadNotificationsCheckAsync.java b/app/src/main/java/fr/free/nrw/commons/notification/UnreadNotificationsCheckAsync.java deleted file mode 100644 index 690754b57..000000000 --- a/app/src/main/java/fr/free/nrw/commons/notification/UnreadNotificationsCheckAsync.java +++ /dev/null @@ -1,81 +0,0 @@ -package fr.free.nrw.commons.notification; - -import android.os.AsyncTask; - -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.Date; -import java.util.List; - -import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.contributions.ContributionsFragment; -import timber.log.Timber; - -/** - * This asynctask will check unread notifications after a date (date user check notifications last) - */ - -public class UnreadNotificationsCheckAsync extends AsyncTask { - - WeakReference context; - NotificationController notificationController; - - - public UnreadNotificationsCheckAsync(MainActivity context, NotificationController notificationController) { - this.context = new WeakReference<>(context); - this.notificationController = notificationController; - } - - @Override - protected Notification doInBackground(Void... voids) { - Notification lastNotification = null; - - try { - lastNotification = findLastNotification(notificationController.getNotifications()); - } catch (IOException e) { - e.printStackTrace(); - } - - return lastNotification; - } - - @Override - protected void onPostExecute(Notification lastNotification) { - super.onPostExecute(lastNotification); - - if (lastNotification == null) { - return; - } - - Date lastNotificationCheckDate = new Date(context.get() - .getSharedPreferences("defaultKvStore",0) - .getLong("last_read_notification_date", 0)); - Timber.d("You may have unread notifications since"+lastNotificationCheckDate); - - boolean isThereUnreadNotifications; - - Date lastReadNotificationDate = new java.util.Date(Long.parseLong(lastNotification.dateWithYear)*1000); - - if (lastNotificationCheckDate.before(lastReadNotificationDate)) { - isThereUnreadNotifications = true; - } else { - isThereUnreadNotifications = false; - } - - // Check if activity is still running - if (context.get().getWindow().getDecorView().isShown() && !context.get().isFinishing()) { - // Check if fragment is not null and visible - if (context.get().isContributionsFragmentVisible && context.get().contributionsActivityPagerAdapter.getItem(0) != null) { - ((ContributionsFragment)(context.get().contributionsActivityPagerAdapter.getItem(0))).updateNotificationsNotification(isThereUnreadNotifications); - } - } - } - - private Notification findLastNotification(List allNotifications) { - if (allNotifications.size() > 0) { - return allNotifications.get(allNotifications.size()-1); - } else { - return null; - } - } -} diff --git a/app/src/main/res/drawable/ic_delete_black_24dp.xml b/app/src/main/res/drawable/ic_delete_black_24dp.xml new file mode 100644 index 000000000..8bed121aa --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_notifications_off_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_off_black_24dp.xml new file mode 100644 index 000000000..f7aa3891d --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_off_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/notification_badge.xml b/app/src/main/res/drawable/notification_badge.xml new file mode 100644 index 000000000..babec7015 --- /dev/null +++ b/app/src/main/res/drawable/notification_badge.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml index f8294eb66..3f4f9c203 100644 --- a/app/src/main/res/layout/activity_notification.xml +++ b/app/src/main/res/layout/activity_notification.xml @@ -1,6 +1,7 @@ @@ -30,6 +31,45 @@ /> + + + + + + + + + + + + + + + + + /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/notification_icon.xml b/app/src/main/res/layout/notification_icon.xml new file mode 100644 index 000000000..5a41d5f80 --- /dev/null +++ b/app/src/main/res/layout/notification_icon.xml @@ -0,0 +1,38 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/contribution_activity_notification_menu.xml b/app/src/main/res/menu/contribution_activity_notification_menu.xml index b480d917b..e4b974a13 100644 --- a/app/src/main/res/menu/contribution_activity_notification_menu.xml +++ b/app/src/main/res/menu/contribution_activity_notification_menu.xml @@ -1,11 +1,11 @@ - + android:title="@string/notifications" + app:showAsAction="ifRoom|withText" + android:menuCategory="secondary" + app:actionLayout="@layout/notification_icon" + /> - + #303030 #fafafa @@ -57,4 +57,6 @@ #757575 #FFFFFF #000000 + + #FF0000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3ca0694cd..7ac904783 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -460,5 +460,7 @@ Upload your first media by touching the camera or gallery icon above. No images used No images reverted No images uploaded - Share logs using + + You have no unread Notification + Share logs using diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ea7bce650..71869afd4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip