Skip to content

Commit 2a0b9d8

Browse files
Vivek Maskaraneslihanturan
Vivek Maskara
authored andcommitted
Enable crosswiki notifications and minor UI fixes in displaying notif… (commons-app#1540)
* Enable crosswiki notifications and minor UI fixes in displaying notifications * Added java docs
1 parent 41acb76 commit 2a0b9d8

11 files changed

+247
-23
lines changed

.travis.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ android:
1919
components:
2020
- tools
2121
- platform-tools
22-
- build-tools-26.0.2
22+
- build-tools-27.0.0
2323
- extra-google-m2repository
2424
- extra-android-m2repository
2525
- ${ANDROID_TARGET}
2626
- android-25
2727
- android-26
28+
- android-27
2829
- sys-img-${ANDROID_ABI}-${ANDROID_TARGET}
2930
licenses:
3031
- 'android-sdk-license-.+'

app/build.gradle

+5-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ dependencies {
6969
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
7070
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
7171

72+
implementation 'com.caverock:androidsvg:1.2.1'
73+
implementation 'com.github.bumptech.glide:glide:4.7.1'
74+
kapt 'com.github.bumptech.glide:compiler:4.7.1'
75+
7276
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
7377
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
7478
androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION"
@@ -117,7 +121,7 @@ android {
117121
buildTypes {
118122
release {
119123
minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient.
120-
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
124+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-glide.txt'
121125
}
122126
debug {
123127
applicationIdSuffix ".debug"

app/proguard-glide.txt

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-keep public class * implements com.bumptech.glide.module.GlideModule
2+
-keep public class * extends com.bumptech.glide.module.AppGlideModule
3+
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
4+
**[] $VALUES;
5+
public *;
6+
}
7+
8+
# for DexGuard only
9+
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package fr.free.nrw.commons.glide;
2+
3+
import android.support.annotation.NonNull;
4+
5+
import com.bumptech.glide.load.Options;
6+
import com.bumptech.glide.load.ResourceDecoder;
7+
import com.bumptech.glide.load.engine.Resource;
8+
import com.bumptech.glide.load.resource.SimpleResource;
9+
import com.caverock.androidsvg.SVG;
10+
import com.caverock.androidsvg.SVGParseException;
11+
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
15+
/**
16+
* Decodes an SVG internal representation from an {@link InputStream}.
17+
*/
18+
public class SvgDecoder implements ResourceDecoder<InputStream, SVG> {
19+
20+
@Override
21+
public boolean handles(@NonNull InputStream source, @NonNull Options options) {
22+
// TODO: Can we tell?
23+
return true;
24+
}
25+
26+
public Resource<SVG> decode(@NonNull InputStream source, int width, int height,
27+
@NonNull Options options)
28+
throws IOException {
29+
try {
30+
SVG svg = SVG.getFromInputStream(source);
31+
return new SimpleResource<>(svg);
32+
} catch (SVGParseException ex) {
33+
throw new IOException("Cannot load SVG from stream", ex);
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package fr.free.nrw.commons.glide;
2+
3+
import android.graphics.Picture;
4+
import android.graphics.drawable.PictureDrawable;
5+
import android.support.annotation.NonNull;
6+
import android.support.annotation.Nullable;
7+
8+
import com.bumptech.glide.load.Options;
9+
import com.bumptech.glide.load.engine.Resource;
10+
import com.bumptech.glide.load.resource.SimpleResource;
11+
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
12+
import com.caverock.androidsvg.SVG;
13+
14+
/**
15+
* Convert the {@link SVG}'s internal representation to an Android-compatible one
16+
* ({@link Picture}).
17+
*/
18+
public class SvgDrawableTranscoder implements ResourceTranscoder<SVG, PictureDrawable> {
19+
@Nullable
20+
@Override
21+
public Resource<PictureDrawable> transcode(@NonNull Resource<SVG> toTranscode,
22+
@NonNull Options options) {
23+
SVG svg = toTranscode.get();
24+
Picture picture = svg.renderToPicture();
25+
PictureDrawable drawable = new PictureDrawable(picture);
26+
return new SimpleResource<>(drawable);
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package fr.free.nrw.commons.glide;
2+
3+
import android.graphics.drawable.PictureDrawable;
4+
import android.widget.ImageView;
5+
6+
import com.bumptech.glide.load.DataSource;
7+
import com.bumptech.glide.load.engine.GlideException;
8+
import com.bumptech.glide.request.RequestListener;
9+
import com.bumptech.glide.request.target.ImageViewTarget;
10+
import com.bumptech.glide.request.target.Target;
11+
12+
/**
13+
* Listener which updates the {@link ImageView} to be software rendered, because
14+
* {@link com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on
15+
* a hardware backed {@link android.graphics.Canvas Canvas}.
16+
*/
17+
public class SvgSoftwareLayerSetter implements RequestListener<PictureDrawable> {
18+
19+
/**
20+
* Sets the layer type to none if the load fails
21+
* @param e
22+
* @param model
23+
* @param target
24+
* @param isFirstResource
25+
* @return
26+
*/
27+
@Override
28+
public boolean onLoadFailed(GlideException e, Object model, Target<PictureDrawable> target,
29+
boolean isFirstResource) {
30+
ImageView view = ((ImageViewTarget<?>) target).getView();
31+
view.setLayerType(ImageView.LAYER_TYPE_NONE, null);
32+
return false;
33+
}
34+
35+
/**
36+
* Sets the layer type to software when the resource is ready
37+
* @param resource
38+
* @param model
39+
* @param target
40+
* @param dataSource
41+
* @param isFirstResource
42+
* @return
43+
*/
44+
@Override
45+
public boolean onResourceReady(PictureDrawable resource, Object model,
46+
Target<PictureDrawable> target, DataSource dataSource, boolean isFirstResource) {
47+
ImageView view = ((ImageViewTarget<?>) target).getView();
48+
view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
49+
return false;
50+
}
51+
}

app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -444,8 +444,8 @@ public List<Notification> getNotifications() {
444444
.param("notprop", "list")
445445
.param("format", "xml")
446446
.param("meta", "notifications")
447-
// .param("meta", "notifications")
448447
.param("notformat", "model")
448+
.param("notwikis", "wikidatawiki|commonswiki|enwiki")
449449
.get()
450450
.getNode("/api/query/notifications/list");
451451
} catch (IOException e) {

app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java

+27-11
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
package fr.free.nrw.commons.notification;
22

3-
import android.util.Log;
3+
import android.graphics.drawable.PictureDrawable;
4+
import android.text.Html;
45
import android.view.LayoutInflater;
56
import android.view.View;
67
import android.view.ViewGroup;
78
import android.widget.ImageView;
89
import android.widget.TextView;
910

1011
import com.borjabravo.readmoretextview.ReadMoreTextView;
12+
import com.bumptech.glide.RequestBuilder;
1113
import com.pedrogomez.renderers.Renderer;
1214

1315
import butterknife.BindView;
1416
import butterknife.ButterKnife;
1517
import fr.free.nrw.commons.R;
18+
import fr.free.nrw.commons.glide.SvgSoftwareLayerSetter;
19+
20+
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
1621

1722
/**
1823
* Created by root on 19.12.2017.
1924
*/
2025

2126
public class NotificationRenderer extends Renderer<Notification> {
27+
private RequestBuilder<PictureDrawable> requestBuilder;
28+
2229
@BindView(R.id.title) ReadMoreTextView title;
2330
@BindView(R.id.time) TextView time;
2431
@BindView(R.id.icon) ImageView icon;
@@ -41,23 +48,32 @@ protected void hookListeners(View rootView) {
4148
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
4249
View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false);
4350
ButterKnife.bind(this, inflatedView);
51+
requestBuilder = GlideApp.with(inflatedView.getContext())
52+
.as(PictureDrawable.class)
53+
.error(R.drawable.round_icon_unknown)
54+
.transition(withCrossFade())
55+
.listener(new SvgSoftwareLayerSetter());
4456
return inflatedView;
4557
}
4658

4759
@Override
4860
public void render() {
4961
Notification notification = getContent();
50-
String str = notification.notificationText.trim();
51-
str = str.concat(" ");
52-
title.setText(str);
62+
setTitle(notification.notificationText);
5363
time.setText(notification.date);
54-
switch (notification.notificationType) {
55-
case THANK_YOU_EDIT:
56-
icon.setImageResource(R.drawable.ic_edit_black_24dp);
57-
break;
58-
default:
59-
icon.setImageResource(R.drawable.round_icon_unknown);
60-
}
64+
requestBuilder.load(notification.iconUrl).into(icon);
65+
}
66+
67+
/**
68+
* Cleans up the notification text and sets it as the title
69+
* Clean up is required to fix escaped HTML string and extra white spaces at the beginning of the notification
70+
* @param notificationText
71+
*/
72+
private void setTitle(String notificationText) {
73+
notificationText = notificationText.trim().replaceAll("(^\\h*)|(\\h*$)", "");
74+
notificationText = Html.fromHtml(notificationText).toString();
75+
notificationText = notificationText.concat(" ");
76+
title.setText(notificationText);
6177
}
6278

6379
public interface NotificationClicked{

app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java

+49-5
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
import fr.free.nrw.commons.BuildConfig;
1717
import fr.free.nrw.commons.R;
1818

19-
import static fr.free.nrw.commons.notification.NotificationType.THANK_YOU_EDIT;
2019
import static fr.free.nrw.commons.notification.NotificationType.UNKNOWN;
2120

2221
public class NotificationUtils {
2322

2423
private static final String COMMONS_WIKI = "commonswiki";
24+
private static final String WIKIDATA_WIKI = "wikidatawiki";
25+
private static final String WIKIPEDIA_WIKI = "enwiki";
2526

2627
public static boolean isCommonsNotification(Node document) {
2728
if (document == null || !document.hasAttributes()) {
@@ -31,6 +32,32 @@ public static boolean isCommonsNotification(Node document) {
3132
return COMMONS_WIKI.equals(element.getAttribute("wiki"));
3233
}
3334

35+
/**
36+
* Returns true if the wiki attribute corresponds to wikidatawiki
37+
* @param document
38+
* @return
39+
*/
40+
public static boolean isWikidataNotification(Node document) {
41+
if (document == null || !document.hasAttributes()) {
42+
return false;
43+
}
44+
Element element = (Element) document;
45+
return WIKIDATA_WIKI.equals(element.getAttribute("wiki"));
46+
}
47+
48+
/**
49+
* Returns true if the wiki attribute corresponds to enwiki
50+
* @param document
51+
* @return
52+
*/
53+
public static boolean isWikipediaNotification(Node document) {
54+
if (document == null || !document.hasAttributes()) {
55+
return false;
56+
}
57+
Element element = (Element) document;
58+
return WIKIPEDIA_WIKI.equals(element.getAttribute("wiki"));
59+
}
60+
3461
public static NotificationType getNotificationType(Node document) {
3562
Element element = (Element) document;
3663
String type = element.getAttribute("type");
@@ -68,10 +95,17 @@ public static List<Notification> getNotificationsFromList(Context context, NodeL
6895
return notifications;
6996
}
7097

98+
/**
99+
* Currently the app is interested in showing notifications just from the following three wikis: commons, wikidata, wikipedia
100+
* This function returns true only if the notification belongs to any of the above wikis and is of a known notification type
101+
* @param node
102+
* @return
103+
*/
71104
private static boolean isUsefulNotification(Node node) {
72-
return isCommonsNotification(node)
73-
&& !getNotificationType(node).equals(UNKNOWN)
74-
&& !getNotificationType(node).equals(THANK_YOU_EDIT);
105+
return (isCommonsNotification(node)
106+
|| isWikidataNotification(node)
107+
|| isWikipediaNotification(node))
108+
&& !getNotificationType(node).equals(UNKNOWN);
75109
}
76110

77111
public static boolean isBundledNotification(Node document) {
@@ -97,7 +131,7 @@ public static Notification getNotificationFromApiResult(Context context, Node do
97131

98132
switch (type) {
99133
case THANK_YOU_EDIT:
100-
notificationText = context.getString(R.string.notifications_thank_you_edit);
134+
notificationText = getThankYouEditDescription(document);
101135
break;
102136
case EDIT_USER_TALK:
103137
notificationText = getNotificationText(document);
@@ -146,6 +180,16 @@ private static String getMentionDescription(Node document) {
146180
return body != null ? body.getTextContent() : "";
147181
}
148182

183+
/**
184+
* Gets the header node returned in the XML document to form the description for thank you edits
185+
* @param document
186+
* @return
187+
*/
188+
private static String getThankYouEditDescription(Node document) {
189+
Node body = getNode(getModel(document), "header");
190+
return body != null ? body.getTextContent() : "";
191+
}
192+
149193
private static String getNotificationIconUrl(Node document) {
150194
String format = "%s%s";
151195
Node iconUrl = getNode(getModel(document), "iconUrl");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package fr.free.nrw.commons.notification;
2+
3+
import android.content.Context;
4+
import android.graphics.drawable.PictureDrawable;
5+
import android.support.annotation.NonNull;
6+
7+
import com.bumptech.glide.Glide;
8+
import com.bumptech.glide.Registry;
9+
import com.bumptech.glide.annotation.GlideModule;
10+
import com.bumptech.glide.module.AppGlideModule;
11+
import com.caverock.androidsvg.SVG;
12+
13+
import java.io.InputStream;
14+
15+
import fr.free.nrw.commons.glide.SvgDecoder;
16+
import fr.free.nrw.commons.glide.SvgDrawableTranscoder;
17+
18+
/**
19+
* Module for the SVG sample app.
20+
*/
21+
@GlideModule
22+
public class SvgModule extends AppGlideModule {
23+
@Override
24+
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
25+
@NonNull Registry registry) {
26+
registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder())
27+
.append(InputStream.class, SVG.class, new SvgDecoder());
28+
}
29+
30+
// Disable manifest parsing to avoid adding similar modules twice.
31+
@Override
32+
public boolean isManifestParsingEnabled() {
33+
return false;
34+
}
35+
}

0 commit comments

Comments
 (0)