mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 14:53:59 +01:00 
			
		
		
		
	Enable crosswiki notifications and minor UI fixes in displaying notif… (#1540)
* Enable crosswiki notifications and minor UI fixes in displaying notifications * Added java docs
This commit is contained in:
		
							parent
							
								
									41acb76bd8
								
							
						
					
					
						commit
						2a0b9d8a0b
					
				
					 11 changed files with 247 additions and 23 deletions
				
			
		
							
								
								
									
										36
									
								
								app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| package fr.free.nrw.commons.glide; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import com.bumptech.glide.load.Options; | ||||
| import com.bumptech.glide.load.ResourceDecoder; | ||||
| import com.bumptech.glide.load.engine.Resource; | ||||
| import com.bumptech.glide.load.resource.SimpleResource; | ||||
| import com.caverock.androidsvg.SVG; | ||||
| import com.caverock.androidsvg.SVGParseException; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| 
 | ||||
| /** | ||||
|  * Decodes an SVG internal representation from an {@link InputStream}. | ||||
|  */ | ||||
| public class SvgDecoder implements ResourceDecoder<InputStream, SVG> { | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean handles(@NonNull InputStream source, @NonNull Options options) { | ||||
|         // TODO: Can we tell? | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public Resource<SVG> decode(@NonNull InputStream source, int width, int height, | ||||
|                                 @NonNull Options options) | ||||
|             throws IOException { | ||||
|         try { | ||||
|             SVG svg = SVG.getFromInputStream(source); | ||||
|             return new SimpleResource<>(svg); | ||||
|         } catch (SVGParseException ex) { | ||||
|             throw new IOException("Cannot load SVG from stream", ex); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,28 @@ | |||
| package fr.free.nrw.commons.glide; | ||||
| 
 | ||||
| import android.graphics.Picture; | ||||
| import android.graphics.drawable.PictureDrawable; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import com.bumptech.glide.load.Options; | ||||
| import com.bumptech.glide.load.engine.Resource; | ||||
| import com.bumptech.glide.load.resource.SimpleResource; | ||||
| import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; | ||||
| import com.caverock.androidsvg.SVG; | ||||
| 
 | ||||
| /** | ||||
|  * Convert the {@link SVG}'s internal representation to an Android-compatible one | ||||
|  * ({@link Picture}). | ||||
|  */ | ||||
| public class SvgDrawableTranscoder implements ResourceTranscoder<SVG, PictureDrawable> { | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public Resource<PictureDrawable> transcode(@NonNull Resource<SVG> toTranscode, | ||||
|                                                @NonNull Options options) { | ||||
|         SVG svg = toTranscode.get(); | ||||
|         Picture picture = svg.renderToPicture(); | ||||
|         PictureDrawable drawable = new PictureDrawable(picture); | ||||
|         return new SimpleResource<>(drawable); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,51 @@ | |||
| package fr.free.nrw.commons.glide; | ||||
| 
 | ||||
| import android.graphics.drawable.PictureDrawable; | ||||
| import android.widget.ImageView; | ||||
| 
 | ||||
| import com.bumptech.glide.load.DataSource; | ||||
| import com.bumptech.glide.load.engine.GlideException; | ||||
| import com.bumptech.glide.request.RequestListener; | ||||
| import com.bumptech.glide.request.target.ImageViewTarget; | ||||
| import com.bumptech.glide.request.target.Target; | ||||
| 
 | ||||
| /** | ||||
|  * Listener which updates the {@link ImageView} to be software rendered, because | ||||
|  * {@link com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on | ||||
|  * a hardware backed {@link android.graphics.Canvas Canvas}. | ||||
|  */ | ||||
| public class SvgSoftwareLayerSetter implements RequestListener<PictureDrawable> { | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the layer type to none if the load fails | ||||
|      * @param e | ||||
|      * @param model | ||||
|      * @param target | ||||
|      * @param isFirstResource | ||||
|      * @return | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean onLoadFailed(GlideException e, Object model, Target<PictureDrawable> target, | ||||
|                                 boolean isFirstResource) { | ||||
|         ImageView view = ((ImageViewTarget<?>) target).getView(); | ||||
|         view.setLayerType(ImageView.LAYER_TYPE_NONE, null); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the layer type to software when the resource is ready | ||||
|      * @param resource | ||||
|      * @param model | ||||
|      * @param target | ||||
|      * @param dataSource | ||||
|      * @param isFirstResource | ||||
|      * @return | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean onResourceReady(PictureDrawable resource, Object model, | ||||
|                                    Target<PictureDrawable> target, DataSource dataSource, boolean isFirstResource) { | ||||
|         ImageView view = ((ImageViewTarget<?>) target).getView(); | ||||
|         view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null); | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | @ -444,8 +444,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|                     .param("notprop", "list") | ||||
|                     .param("format", "xml") | ||||
|                     .param("meta", "notifications") | ||||
| //                    .param("meta", "notifications") | ||||
|                     .param("notformat", "model") | ||||
|                     .param("notwikis", "wikidatawiki|commonswiki|enwiki") | ||||
|                     .get() | ||||
|                     .getNode("/api/query/notifications/list"); | ||||
|         } catch (IOException e) { | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package fr.free.nrw.commons.notification; | ||||
| 
 | ||||
| import android.util.Log; | ||||
| import android.graphics.drawable.PictureDrawable; | ||||
| import android.text.Html; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
|  | @ -8,17 +9,23 @@ import android.widget.ImageView; | |||
| import android.widget.TextView; | ||||
| 
 | ||||
| import com.borjabravo.readmoretextview.ReadMoreTextView; | ||||
| import com.bumptech.glide.RequestBuilder; | ||||
| import com.pedrogomez.renderers.Renderer; | ||||
| 
 | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.glide.SvgSoftwareLayerSetter; | ||||
| 
 | ||||
| import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; | ||||
| 
 | ||||
| /** | ||||
|  * Created by root on 19.12.2017. | ||||
|  */ | ||||
| 
 | ||||
| public class NotificationRenderer extends Renderer<Notification> { | ||||
|     private RequestBuilder<PictureDrawable> requestBuilder; | ||||
| 
 | ||||
|     @BindView(R.id.title) ReadMoreTextView title; | ||||
|     @BindView(R.id.time) TextView time; | ||||
|     @BindView(R.id.icon) ImageView icon; | ||||
|  | @ -41,23 +48,32 @@ 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); | ||||
|         requestBuilder = GlideApp.with(inflatedView.getContext()) | ||||
|                 .as(PictureDrawable.class) | ||||
|                 .error(R.drawable.round_icon_unknown) | ||||
|                 .transition(withCrossFade()) | ||||
|                 .listener(new SvgSoftwareLayerSetter()); | ||||
|         return inflatedView; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void render() { | ||||
|         Notification notification = getContent(); | ||||
|         String str = notification.notificationText.trim(); | ||||
|         str = str.concat(" "); | ||||
|         title.setText(str); | ||||
|         setTitle(notification.notificationText); | ||||
|         time.setText(notification.date); | ||||
|         switch (notification.notificationType) { | ||||
|             case THANK_YOU_EDIT: | ||||
|                 icon.setImageResource(R.drawable.ic_edit_black_24dp); | ||||
|                 break; | ||||
|             default: | ||||
|                 icon.setImageResource(R.drawable.round_icon_unknown); | ||||
|         } | ||||
|         requestBuilder.load(notification.iconUrl).into(icon); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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("(^\\h*)|(\\h*$)", ""); | ||||
|         notificationText = Html.fromHtml(notificationText).toString(); | ||||
|         notificationText = notificationText.concat(" "); | ||||
|         title.setText(notificationText); | ||||
|     } | ||||
| 
 | ||||
|     public interface NotificationClicked{ | ||||
|  |  | |||
|  | @ -16,12 +16,13 @@ import javax.annotation.Nullable; | |||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.R; | ||||
| 
 | ||||
| import static fr.free.nrw.commons.notification.NotificationType.THANK_YOU_EDIT; | ||||
| import static fr.free.nrw.commons.notification.NotificationType.UNKNOWN; | ||||
| 
 | ||||
| public class NotificationUtils { | ||||
| 
 | ||||
|     private static final String COMMONS_WIKI = "commonswiki"; | ||||
|     private static final String WIKIDATA_WIKI = "wikidatawiki"; | ||||
|     private static final String WIKIPEDIA_WIKI = "enwiki"; | ||||
| 
 | ||||
|     public static boolean isCommonsNotification(Node document) { | ||||
|         if (document == null || !document.hasAttributes()) { | ||||
|  | @ -31,6 +32,32 @@ public class NotificationUtils { | |||
|         return COMMONS_WIKI.equals(element.getAttribute("wiki")); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if the wiki attribute corresponds to wikidatawiki | ||||
|      * @param document | ||||
|      * @return | ||||
|      */ | ||||
|     public static boolean isWikidataNotification(Node document) { | ||||
|         if (document == null || !document.hasAttributes()) { | ||||
|             return false; | ||||
|         } | ||||
|         Element element = (Element) document; | ||||
|         return WIKIDATA_WIKI.equals(element.getAttribute("wiki")); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if the wiki attribute corresponds to enwiki | ||||
|      * @param document | ||||
|      * @return | ||||
|      */ | ||||
|     public static boolean isWikipediaNotification(Node document) { | ||||
|         if (document == null || !document.hasAttributes()) { | ||||
|             return false; | ||||
|         } | ||||
|         Element element = (Element) document; | ||||
|         return WIKIPEDIA_WIKI.equals(element.getAttribute("wiki")); | ||||
|     } | ||||
| 
 | ||||
|     public static NotificationType getNotificationType(Node document) { | ||||
|         Element element = (Element) document; | ||||
|         String type = element.getAttribute("type"); | ||||
|  | @ -68,10 +95,17 @@ public class NotificationUtils { | |||
|         return notifications; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Currently the app is interested in showing notifications just from the following three wikis: commons, wikidata, wikipedia | ||||
|      * This function returns true only if the notification belongs to any of the above wikis and is of a known notification type | ||||
|      * @param node | ||||
|      * @return | ||||
|      */ | ||||
|     private static boolean isUsefulNotification(Node node) { | ||||
|         return isCommonsNotification(node) | ||||
|                 && !getNotificationType(node).equals(UNKNOWN) | ||||
|                 && !getNotificationType(node).equals(THANK_YOU_EDIT); | ||||
|         return (isCommonsNotification(node) | ||||
|                 || isWikidataNotification(node) | ||||
|                 || isWikipediaNotification(node)) | ||||
|                 && !getNotificationType(node).equals(UNKNOWN); | ||||
|     } | ||||
| 
 | ||||
|     public static boolean isBundledNotification(Node document) { | ||||
|  | @ -97,7 +131,7 @@ public class NotificationUtils { | |||
| 
 | ||||
|         switch (type) { | ||||
|             case THANK_YOU_EDIT: | ||||
|                 notificationText = context.getString(R.string.notifications_thank_you_edit); | ||||
|                 notificationText = getThankYouEditDescription(document); | ||||
|                 break; | ||||
|             case EDIT_USER_TALK: | ||||
|                 notificationText = getNotificationText(document); | ||||
|  | @ -146,6 +180,16 @@ public class NotificationUtils { | |||
|         return body != null ? body.getTextContent() : ""; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gets the header node returned in the XML document to form the description for thank you edits | ||||
|      * @param document | ||||
|      * @return | ||||
|      */ | ||||
|     private static String getThankYouEditDescription(Node document) { | ||||
|         Node body = getNode(getModel(document), "header"); | ||||
|         return body != null ? body.getTextContent() : ""; | ||||
|     } | ||||
| 
 | ||||
|     private static String getNotificationIconUrl(Node document) { | ||||
|         String format = "%s%s"; | ||||
|         Node iconUrl = getNode(getModel(document), "iconUrl"); | ||||
|  |  | |||
|  | @ -0,0 +1,35 @@ | |||
| package fr.free.nrw.commons.notification; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.graphics.drawable.PictureDrawable; | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import com.bumptech.glide.Glide; | ||||
| import com.bumptech.glide.Registry; | ||||
| import com.bumptech.glide.annotation.GlideModule; | ||||
| import com.bumptech.glide.module.AppGlideModule; | ||||
| import com.caverock.androidsvg.SVG; | ||||
| 
 | ||||
| import java.io.InputStream; | ||||
| 
 | ||||
| import fr.free.nrw.commons.glide.SvgDecoder; | ||||
| import fr.free.nrw.commons.glide.SvgDrawableTranscoder; | ||||
| 
 | ||||
| /** | ||||
|  * Module for the SVG sample app. | ||||
|  */ | ||||
| @GlideModule | ||||
| public class SvgModule extends AppGlideModule { | ||||
|     @Override | ||||
|     public void registerComponents(@NonNull Context context, @NonNull Glide glide, | ||||
|                                    @NonNull Registry registry) { | ||||
|         registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder()) | ||||
|                 .append(InputStream.class, SVG.class, new SvgDecoder()); | ||||
|     } | ||||
| 
 | ||||
|     // Disable manifest parsing to avoid adding similar modules twice. | ||||
|     @Override | ||||
|     public boolean isManifestParsingEnabled() { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vivek Maskara
						Vivek Maskara