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
This commit is contained in:
Vanshika Arora 2019-02-02 19:21:58 +05:30 committed by Vivek Maskara
parent 9451b00a15
commit 1b62ac4d2d
22 changed files with 381 additions and 195 deletions

View file

@ -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 {

View file

@ -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<DataSetObserver> 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();

View file

@ -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<Notification> 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 {
}

View file

@ -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,

View file

@ -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.

View file

@ -84,6 +84,9 @@ public interface MediaWikiApi {
@NonNull
List<Notification> getNotifications() throws IOException;
@NonNull
boolean markNotificationAsRead(Notification notification) throws IOException;
@NonNull
Observable<String> searchTitles(String title, int searchCatsLimit);

View file

@ -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 + '\'' +
'}';
}
}

View file

@ -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<Notification> adapter;
private List<Notification> 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<Notification> 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<Notification> 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);
}
}

View file

@ -36,4 +36,7 @@ public class NotificationController {
}
return new ArrayList<>();
}
public boolean markAsRead(Notification notification) throws IOException{
return mediaWikiApi.markNotificationAsRead(notification);
}
}

View file

@ -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<Notification> {
@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<Notification> {
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<Notification> {
/**
* 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);
}
}

View file

@ -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<Notification> 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) {

View file

@ -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<Void, Void, Notification> {
WeakReference<MainActivity> 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<Notification> allNotifications) {
if (allNotifications.size() > 0) {
return allNotifications.get(allNotifications.size()-1);
} else {
return null;
}
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#959594"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20,18.69L7.84,6.14 5.27,3.49 4,4.76l2.8,2.8v0.01c-0.52,0.99 -0.8,2.16 -0.8,3.42v5l-2,2v1h13.73l2,2L21,19.72l-1,-1.03zM12,22c1.11,0 2,-0.89 2,-2h-4c0,1.11 0.89,2 2,2zM18,14.68L18,11c0,-3.08 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68c-0.15,0.03 -0.29,0.08 -0.42,0.12 -0.1,0.03 -0.2,0.07 -0.3,0.11h-0.01c-0.01,0 -0.01,0 -0.02,0.01 -0.23,0.09 -0.46,0.2 -0.68,0.31 0,0 -0.01,0 -0.01,0.01L18,14.68z"/>
</vector>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#F00" />
<size
android:width="12dp"
android:height="12dp" />
</shape>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -30,6 +31,45 @@
/>
</RelativeLayout>
<android.support.constraint.ConstraintLayout
android:id="@+id/no_notification_background"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="118dp"
android:layout_height="172dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.763"
app:srcCompat="@drawable/ic_notifications_off_black_24dp" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="188dp"
android:text="@string/no_notification"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
<android.support.design.widget.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"

View file

@ -1,10 +1,36 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:foreground="?selectableItemBackground"
android:minHeight="72dp">
<com.daimajia.swipe.SwipeLayout android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/swipeLayout"
>
<LinearLayout
android:id="@+id/bottom"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/bottom_wrapper_child1"
android:background="@color/deleteRed"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/star"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
app:srcCompat="@drawable/ic_done_black_24dp"
android:layout_width="20dp"
android:layout_height="20dp" />
</RelativeLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/icon"
@ -16,9 +42,9 @@
android:background="@android:color/white"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_message_black_24dp"
app:tint="@color/primaryDarkColor"
/>
/>
<!--app:tint="@color/primaryDarkColor"-->
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
@ -47,4 +73,6 @@
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:padding="12dp"
/>
</RelativeLayout>
</com.daimajia.swipe.SwipeLayout>
</RelativeLayout>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="true"
android:gravity="center"
tools:background="@color/black">
<ImageView
android:id="@+id/cart_icon_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:gravity="center"
android:layout_marginRight="8dp"
app:srcCompat="@drawable/ic_notifications_white_24dp" />
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notification_count_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/cart_icon_image_view"
android:layout_alignEnd="@id/cart_icon_image_view"
android:layout_alignRight="@id/cart_icon_image_view"
android:background="@drawable/notification_badge"
android:gravity="center"
android:padding="2dp"
android:textColor="#ffffffff"
android:textSize="7sp"
android:textStyle="bold"
android:visibility="gone"
tools:text="9+"
tools:visibility="visible" />
</RelativeLayout>

View file

@ -1,11 +1,11 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/notifications"
android:title="@string/notifications"
app:showAsAction="ifRoom|withText"
android:icon="@drawable/ic_notifications_white_24dp"
/>
android:title="@string/notifications"
app:showAsAction="ifRoom|withText"
android:menuCategory="secondary"
app:actionLayout="@layout/notification_icon"
/>
<item android:id="@+id/list_sheet"
android:title="@string/list_sheet"
app:showAsAction="ifRoom|withText"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Main application background color -->
<color name="main_background_dark">#303030</color>
<color name="main_background_light">#fafafa</color>
@ -57,4 +57,6 @@
<color name="opak_middle_grey">#757575</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="swipe_red" tools:ignore="MissingDefaultResource">#FF0000</color>
</resources>

View file

@ -460,5 +460,7 @@ Upload your first media by touching the camera or gallery icon above.</string>
<string name="no_image">No images used</string>
<string name="no_image_reverted">No images reverted</string>
<string name="no_image_uploaded">No images uploaded</string>
<string name="share_logs_using">Share logs using</string>
<string name="no_notification">You have no unread Notification</string>
<string name="share_logs_using">Share logs using</string>
</resources>

View file

@ -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