diff --git a/app/build.gradle b/app/build.gradle index 33d9f4a2f..bbdd3870b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,6 @@ apply plugin: 'jacoco-android' apply from: 'quality.gradle' dependencies { - implementation 'com.prof.rssparser:rssparser:1.1' implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07' implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar' implementation 'in.yuvi:http.fluent:1.3' @@ -43,7 +42,6 @@ dependencies { implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.1.1' implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.1.1' implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.1.1' - implementation 'org.jsoup:jsoup:1.11.3' implementation 'com.facebook.fresco:fresco:1.10.0' implementation 'com.facebook.stetho:stetho:1.5.0' implementation "com.google.dagger:dagger:$DAGGER_VERSION" diff --git a/app/src/main/java/fr/free/nrw/commons/achievements/AchievementsActivity.java b/app/src/main/java/fr/free/nrw/commons/achievements/AchievementsActivity.java index 73143fb80..41df161db 100644 --- a/app/src/main/java/fr/free/nrw/commons/achievements/AchievementsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/achievements/AchievementsActivity.java @@ -36,6 +36,7 @@ import javax.inject.Inject; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; +import fr.free.nrw.commons.Media; import fr.free.nrw.commons.R; import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.auth.SessionManager; diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImageUtils.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImageUtils.java index 941201235..fdcae3aad 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryImageUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImageUtils.java @@ -1,6 +1,5 @@ package fr.free.nrw.commons.category; -import org.jsoup.Jsoup; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -15,6 +14,7 @@ import java.util.List; import javax.annotation.Nullable; import fr.free.nrw.commons.Media; +import fr.free.nrw.commons.utils.StringUtils; import timber.log.Timber; public class CategoryImageUtils { @@ -57,7 +57,7 @@ public class CategoryImageUtils { * @param node * @return */ - private static Media getMediaFromPage(Node node) { + public static Media getMediaFromPage(Node node) { Media media = new Media(null, getImageUrl(node), getFileName(node), @@ -103,17 +103,12 @@ public class CategoryImageUtils { /** * Returns the parsed value of artist from the response - * The artist information is returned as a HTML string from the API. Jsoup library parses the HTML string - * to extract just the text value + * The artist information is returned as a HTML string from the API. Using HTML parser to parse the HTML * @param document * @return */ private static String getCreator(Node document) { - String artist = getMetaDataValue(document, "Artist"); - if (artist != null) { - return Jsoup.parse(artist).text(); - } - return null; + return StringUtils.getParsedStringFromHtml(getMetaDataValue(document, "Artist")); } /** diff --git a/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java index f8c54905e..23c23083b 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java @@ -2,6 +2,7 @@ package fr.free.nrw.commons.category; import android.app.Activity; import android.content.Context; +import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,10 +11,12 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import fr.free.nrw.commons.Media; import fr.free.nrw.commons.MediaWikiImageView; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.ui.widget.HtmlTextView; /** * This is created to only display UI implementation. Needs to be changed in real implementation @@ -92,7 +95,9 @@ public class GridViewAdapter extends ArrayAdapter { private void setAuthorView(Media item, TextView author) { if (item.getCreator() != null && !item.getCreator().equals("")) { String uploadedByTemplate = context.getString(R.string.image_uploaded_by); - author.setText(String.format(uploadedByTemplate, item.getCreator())); + + String uploadedBy = String.format(Locale.getDefault(), uploadedByTemplate, item.getCreator()); + author.setText(uploadedBy); } else { author.setVisibility(View.GONE); } diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java index 43721a217..055fa3737 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java @@ -15,6 +15,7 @@ import fr.free.nrw.commons.modifications.ModificationsSyncAdapter; import fr.free.nrw.commons.nearby.PlaceRenderer; import fr.free.nrw.commons.upload.FileProcessor; import fr.free.nrw.commons.settings.SettingsFragment; +import fr.free.nrw.commons.widget.PicOfDayAppWidget; @Singleton @@ -50,6 +51,8 @@ public interface CommonsApplicationComponent extends AndroidInjector getPictureOfTheDay() { + return Single.fromCallable(() -> { + CustomApiResult apiResult = null; + try { + String template = "Template:Potd/" + DateUtils.getCurrentDate(); + CustomMwApi.RequestBuilder requestBuilder = api.action("query") + .param("generator", "images") + .param("format", "xml") + .param("titles", template) + .param("prop", "imageinfo") + .param("iiprop", "url|extmetadata"); + + apiResult = requestBuilder.get(); + } catch (IOException e) { + Timber.e("Failed to obtain searchCategories", e); + } + + if (apiResult == null) { + return null; + } + + CustomApiResult imageNode = apiResult.getNode("/api/query/pages/page"); + if (imageNode == null + || imageNode.getDocument() == null) { + return null; + } + + return CategoryImageUtils.getMediaFromPage(imageNode.getDocument()); + }); + } + private Date parseMWDate(String mwDate) { SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC isoFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 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 805aab056..d7bf65802 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 @@ -101,6 +101,8 @@ public interface MediaWikiApi { Single getAchievements(String userName); + Single getPictureOfTheDay(); + void logout(); interface ProgressListener { diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DateUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/DateUtils.java index 6b3bf0377..b9aebb3c0 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/DateUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/DateUtils.java @@ -1,7 +1,9 @@ package fr.free.nrw.commons.utils; +import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import java.util.Locale; public class DateUtils { public static String getTimeAgo(Date currDate, Date itemDate) { @@ -33,4 +35,10 @@ public class DateUtils { return "0-seconds"; } } + + public static String getCurrentDate() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + Date date = new Date(); + return dateFormat.format(date); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/utils/StringUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/StringUtils.java new file mode 100644 index 000000000..0eb8216e4 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/StringUtils.java @@ -0,0 +1,15 @@ +package fr.free.nrw.commons.utils; + +import android.os.Build; +import android.text.Html; + +public class StringUtils { + public static String getParsedStringFromHtml(String source) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY).toString(); + } else { + //noinspection deprecation + return Html.fromHtml(source).toString(); + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/widget/PicOfDayAppWidget.java b/app/src/main/java/fr/free/nrw/commons/widget/PicOfDayAppWidget.java index c7c0cfb81..770a9d218 100644 --- a/app/src/main/java/fr/free/nrw/commons/widget/PicOfDayAppWidget.java +++ b/app/src/main/java/fr/free/nrw/commons/widget/PicOfDayAppWidget.java @@ -19,73 +19,102 @@ import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; -import com.prof.rssparser.Article; -import com.prof.rssparser.Parser; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.select.Elements; +import javax.inject.Inject; -import java.util.ArrayList; - -import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.di.ApplicationlessInjection; +import fr.free.nrw.commons.mwapi.MediaWikiApi; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; /** * Implementation of App Widget functionality. */ public class PicOfDayAppWidget extends AppWidgetProvider { - static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { + private CompositeDisposable compositeDisposable = new CompositeDisposable(); + + @Inject + MediaWikiApi mediaWikiApi; + + void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.pic_of_day_app_widget); + loadPictureOfTheDay(context, views, appWidgetManager, appWidgetId); + } - String urlString = BuildConfig.WIKIMEDIA_API_POTD; - Parser parser = new Parser(); - parser.execute(urlString); - parser.onFinish(new Parser.OnTaskCompleted() { + /** + * Loads the picture of the day using media wiki API + * @param context + * @param views + * @param appWidgetManager + * @param appWidgetId + */ + private void loadPictureOfTheDay(Context context, + RemoteViews views, + AppWidgetManager appWidgetManager, + int appWidgetId) { + compositeDisposable.add(mediaWikiApi.getPictureOfTheDay() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + response -> { + if (response != null) { + loadImageFromUrl(response.getImageUrl(), context, views, appWidgetManager, appWidgetId); + } + }, + t -> { + Timber.e(t, "Fetching picture of the day failed"); + } + )); + } + + /** + * Uses Fresco to load an image from Url + * @param imageUrl + * @param context + * @param views + * @param appWidgetManager + * @param appWidgetId + */ + private void loadImageFromUrl(String imageUrl, + Context context, + RemoteViews views, + AppWidgetManager appWidgetManager, + int appWidgetId) { + ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageUrl)).build(); + ImagePipeline imagePipeline = Fresco.getImagePipeline(); + DataSource> dataSource + = imagePipeline.fetchDecodedImage(request, context); + dataSource.subscribe(new BaseBitmapDataSubscriber() { @Override - public void onTaskCompleted(ArrayList
list) { - String desc = list.get(list.size() - 1).getDescription(); - if (desc != null) { - Document document = Jsoup.parse(desc); - Elements elements = document.select("img"); - String imageUrl = elements.get(0).attr("src"); - if (imageUrl != null && imageUrl.length() > 0) { - - ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageUrl)).build(); - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - DataSource> dataSource - = imagePipeline.fetchDecodedImage(request, context); - dataSource.subscribe(new BaseBitmapDataSubscriber() { - @Override - protected void onNewResultImpl(@Nullable Bitmap tempBitmap) { - Bitmap bitmap = null; - if (tempBitmap != null) { - bitmap = Bitmap.createBitmap(tempBitmap.getWidth(), tempBitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - canvas.drawBitmap(tempBitmap, 0f, 0f, new Paint()); - } - views.setImageViewBitmap(R.id.appwidget_image, bitmap); - appWidgetManager.updateAppWidget(appWidgetId, views); - } - - @Override - protected void onFailureImpl(DataSource> dataSource) { - // Ignore failure for now. - } - }, CallerThreadExecutor.getInstance()); - } + protected void onNewResultImpl(@Nullable Bitmap tempBitmap) { + Bitmap bitmap = null; + if (tempBitmap != null) { + bitmap = Bitmap.createBitmap(tempBitmap.getWidth(), tempBitmap.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.drawBitmap(tempBitmap, 0f, 0f, new Paint()); } + views.setImageViewBitmap(R.id.appwidget_image, bitmap); + appWidgetManager.updateAppWidget(appWidgetId, views); } @Override - public void onError() { + protected void onFailureImpl(DataSource> dataSource) { + // Ignore failure for now. } - }); + }, CallerThreadExecutor.getInstance()); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + ApplicationlessInjection + .getInstance(context + .getApplicationContext()) + .getCommonsApplicationComponent() + .inject(this); // There may be multiple widgets active, so update all of them for (int appWidgetId : appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId);