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 1390bd8ef4..0d847b6493 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 @@ -2,7 +2,6 @@ import com.google.gson.Gson; -import fr.free.nrw.commons.actions.PageEditClient; import fr.free.nrw.commons.explore.categories.CategoriesModule; import fr.free.nrw.commons.navtab.MoreBottomSheetFragment; import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment; diff --git a/app/src/main/java/fr/free/nrw/commons/widget/HeightLimitedRecyclerView.java b/app/src/main/java/fr/free/nrw/commons/widget/HeightLimitedRecyclerView.java deleted file mode 100644 index 5c6dde5fd2..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/widget/HeightLimitedRecyclerView.java +++ /dev/null @@ -1,48 +0,0 @@ -package fr.free.nrw.commons.widget; - -import android.app.Activity; -import android.content.Context; -import android.util.AttributeSet; -import android.util.DisplayMetrics; - -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; - -/** - * Created by Ilgaz Er on 8/7/2018. - */ -public class HeightLimitedRecyclerView extends RecyclerView { - int height; - public HeightLimitedRecyclerView(Context context) { - super(context); - DisplayMetrics displayMetrics = new DisplayMetrics(); - ((Activity) getContext()).getWindowManager() - .getDefaultDisplay() - .getMetrics(displayMetrics); - height=displayMetrics.heightPixels; - } - - public HeightLimitedRecyclerView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - DisplayMetrics displayMetrics = new DisplayMetrics(); - ((Activity) getContext()).getWindowManager() - .getDefaultDisplay() - .getMetrics(displayMetrics); - height=displayMetrics.heightPixels; - } - - public HeightLimitedRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - DisplayMetrics displayMetrics = new DisplayMetrics(); - ((Activity) getContext()).getWindowManager() - .getDefaultDisplay() - .getMetrics(displayMetrics); - height=displayMetrics.heightPixels; - } - - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - heightSpec = MeasureSpec.makeMeasureSpec((int) (height*0.3), MeasureSpec.AT_MOST); - super.onMeasure(widthSpec, heightSpec); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/widget/HeightLimitedRecyclerView.kt b/app/src/main/java/fr/free/nrw/commons/widget/HeightLimitedRecyclerView.kt new file mode 100644 index 0000000000..b864552435 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/widget/HeightLimitedRecyclerView.kt @@ -0,0 +1,43 @@ +package fr.free.nrw.commons.widget + +import android.app.Activity +import android.content.Context +import android.util.AttributeSet +import android.util.DisplayMetrics + +import androidx.annotation.Nullable +import androidx.recyclerview.widget.RecyclerView + + +/** + * Created by Ilgaz Er on 8/7/2018. + */ +class HeightLimitedRecyclerView : RecyclerView { + private var height: Int = 0 + + constructor(context: Context) : super(context) { + initializeHeight(context) + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + initializeHeight(context) + } + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) { + initializeHeight(context) + } + + private fun initializeHeight(context: Context) { + val displayMetrics = DisplayMetrics() + (context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics) + height = displayMetrics.heightPixels + } + + override fun onMeasure(widthSpec: Int, heightSpec: Int) { + val limitedHeightSpec = MeasureSpec.makeMeasureSpec( + (height * 0.3).toInt(), + MeasureSpec.AT_MOST + ) + super.onMeasure(widthSpec, limitedHeightSpec) + } +} 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 deleted file mode 100644 index 2734520787..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/widget/PicOfDayAppWidget.java +++ /dev/null @@ -1,181 +0,0 @@ -package fr.free.nrw.commons.widget; - -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.net.Uri; -import android.os.Build; -import android.widget.RemoteViews; -import androidx.annotation.Nullable; -import com.facebook.common.executors.CallerThreadExecutor; -import com.facebook.common.references.CloseableReference; -import com.facebook.datasource.DataSource; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.imagepipeline.core.ImagePipeline; -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 fr.free.nrw.commons.media.MediaClient; -import javax.inject.Inject; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.di.ApplicationlessInjection; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; -import timber.log.Timber; - -import static android.content.Intent.ACTION_VIEW; - -/** - * Implementation of App Widget functionality. - */ -public class PicOfDayAppWidget extends AppWidgetProvider { - - private final CompositeDisposable compositeDisposable = new CompositeDisposable(); - - @Inject - MediaClient mediaClient; - - void updateAppWidget( - final Context context, - final AppWidgetManager appWidgetManager, - final int appWidgetId - ) { - final RemoteViews views = new RemoteViews( - context.getPackageName(), R.layout.pic_of_day_app_widget); - - // Launch App on Button Click - final Intent viewIntent = new Intent(context, MainActivity.class); - int flags = PendingIntent.FLAG_UPDATE_CURRENT; - if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.M) { - flags |= PendingIntent.FLAG_IMMUTABLE; - } - final PendingIntent pendingIntent = PendingIntent.getActivity( - context, 0, viewIntent, flags); - - views.setOnClickPendingIntent(R.id.camera_button, pendingIntent); - appWidgetManager.updateAppWidget(appWidgetId, views); - - loadPictureOfTheDay(context, views, appWidgetManager, appWidgetId); - } - - /** - * Loads the picture of the day using media wiki API - * @param context The application context. - * @param views The RemoteViews object used to update the App Widget UI. - * @param appWidgetManager The AppWidgetManager instance for managing the widget. - * @param appWidgetId he ID of the App Widget to update. - */ - private void loadPictureOfTheDay( - final Context context, - final RemoteViews views, - final AppWidgetManager appWidgetManager, - final int appWidgetId - ) { - compositeDisposable.add(mediaClient.getPictureOfTheDay() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - response -> { - if (response != null) { - views.setTextViewText(R.id.appwidget_title, response.getDisplayTitle()); - - // View in browser - final Intent viewIntent = new Intent(); - viewIntent.setAction(ACTION_VIEW); - viewIntent.setData(Uri.parse(response.getPageTitle().getMobileUri())); - - int flags = PendingIntent.FLAG_UPDATE_CURRENT; - if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.M) { - flags |= PendingIntent.FLAG_IMMUTABLE; - } - final PendingIntent pendingIntent = PendingIntent.getActivity( - context, 0, viewIntent, flags); - - views.setOnClickPendingIntent(R.id.appwidget_image, pendingIntent); - loadImageFromUrl(response.getThumbUrl(), - context, views, appWidgetManager, appWidgetId); - } - }, - t -> Timber.e(t, "Fetching picture of the day failed") - )); - } - - /** - * Uses Fresco to load an image from Url - * @param imageUrl The URL of the image to load. - * @param context The application context. - * @param views The RemoteViews object used to update the App Widget UI. - * @param appWidgetManager The AppWidgetManager instance for managing the widget. - * @param appWidgetId he ID of the App Widget to update. - */ - private void loadImageFromUrl( - final String imageUrl, - final Context context, - final RemoteViews views, - final AppWidgetManager appWidgetManager, - final int appWidgetId - ) { - final ImageRequest request = ImageRequestBuilder - .newBuilderWithSource(Uri.parse(imageUrl)).build(); - final ImagePipeline imagePipeline = Fresco.getImagePipeline(); - final DataSource> dataSource = imagePipeline - .fetchDecodedImage(request, context); - - dataSource.subscribe(new BaseBitmapDataSubscriber() { - @Override - protected void onNewResultImpl(@Nullable final Bitmap tempBitmap) { - Bitmap bitmap = null; - if (tempBitmap != null) { - bitmap = Bitmap.createBitmap( - tempBitmap.getWidth(), tempBitmap.getHeight(), Bitmap.Config.ARGB_8888 - ); - final 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( - final DataSource> dataSource - ) { - // Ignore failure for now. - } - }, CallerThreadExecutor.getInstance()); - } - - @Override - public void onUpdate( - final Context context, - final AppWidgetManager appWidgetManager, - final int[] appWidgetIds - ) { - ApplicationlessInjection - .getInstance(context.getApplicationContext()) - .getCommonsApplicationComponent() - .inject(this); - // There may be multiple widgets active, so update all of them - for (final int appWidgetId : appWidgetIds) { - updateAppWidget(context, appWidgetManager, appWidgetId); - } - } - - @Override - public void onEnabled(final Context context) { - // Enter relevant functionality for when the first widget is created - } - - @Override - public void onDisabled(final Context context) { - // Enter relevant functionality for when the last widget is disabled - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/widget/PicOfDayAppWidget.kt b/app/src/main/java/fr/free/nrw/commons/widget/PicOfDayAppWidget.kt new file mode 100644 index 0000000000..ab6a45b857 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/widget/PicOfDayAppWidget.kt @@ -0,0 +1,174 @@ +package fr.free.nrw.commons.widget + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.net.Uri +import android.os.Build +import android.widget.RemoteViews +import androidx.annotation.Nullable +import com.facebook.common.executors.CallerThreadExecutor +import com.facebook.common.references.CloseableReference +import com.facebook.datasource.DataSource +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.imagepipeline.core.ImagePipeline +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 fr.free.nrw.commons.media.MediaClient +import javax.inject.Inject +import fr.free.nrw.commons.R +import fr.free.nrw.commons.contributions.MainActivity +import fr.free.nrw.commons.di.ApplicationlessInjection +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. + */ +class PicOfDayAppWidget : AppWidgetProvider() { + + private val compositeDisposable = CompositeDisposable() + + @Inject + lateinit var mediaClient: MediaClient + + private fun updateAppWidget( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetId: Int + ) { + val views = RemoteViews(context.packageName, R.layout.pic_of_day_app_widget) + + // Launch App on Button Click + val viewIntent = Intent(context, MainActivity::class.java) + var flags = PendingIntent.FLAG_UPDATE_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + flags = flags or PendingIntent.FLAG_IMMUTABLE + } + val pendingIntent = PendingIntent.getActivity(context, 0, viewIntent, flags) + views.setOnClickPendingIntent(R.id.camera_button, pendingIntent) + + appWidgetManager.updateAppWidget(appWidgetId, views) + + loadPictureOfTheDay(context, views, appWidgetManager, appWidgetId) + } + + /** + * Loads the picture of the day using media wiki API + * @param context The application context. + * @param views The RemoteViews object used to update the App Widget UI. + * @param appWidgetManager The AppWidgetManager instance for managing the widget. + * @param appWidgetId The ID of the App Widget to update. + */ + private fun loadPictureOfTheDay( + context: Context, + views: RemoteViews, + appWidgetManager: AppWidgetManager, + appWidgetId: Int + ) { + compositeDisposable.add( + mediaClient.getPictureOfTheDay() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { response -> + if (response != null) { + views.setTextViewText(R.id.appwidget_title, response.displayTitle) + + // View in browser + val viewIntent = Intent().apply { + action = Intent.ACTION_VIEW + data = Uri.parse(response.pageTitle.mobileUri) + } + + var flags = PendingIntent.FLAG_UPDATE_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + flags = flags or PendingIntent.FLAG_IMMUTABLE + } + val pendingIntent = PendingIntent.getActivity( + context, + 0, + viewIntent, + flags + ) + + views.setOnClickPendingIntent(R.id.appwidget_image, pendingIntent) + loadImageFromUrl( + response.thumbUrl, + context, + views, + appWidgetManager, + appWidgetId + ) + } + }, + { t -> Timber.e(t, "Fetching picture of the day failed") } + ) + ) + } + + /** + * Uses Fresco to load an image from Url + * @param imageUrl The URL of the image to load. + * @param context The application context. + * @param views The RemoteViews object used to update the App Widget UI. + * @param appWidgetManager The AppWidgetManager instance for managing the widget. + * @param appWidgetId The ID of the App Widget to update. + */ + private fun loadImageFromUrl( + imageUrl: String?, + context: Context, + views: RemoteViews, + appWidgetManager: AppWidgetManager, + appWidgetId: Int + ) { + val request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageUrl)).build() + val imagePipeline = Fresco.getImagePipeline() + val dataSource = imagePipeline.fetchDecodedImage(request, context) + + dataSource.subscribe(object : BaseBitmapDataSubscriber() { + override fun onNewResultImpl(tempBitmap: Bitmap?) { + val bitmap = tempBitmap?.let { + Bitmap.createBitmap(it.width, it.height, Bitmap.Config.ARGB_8888).apply { + Canvas(this).drawBitmap(it, 0f, 0f, Paint()) + } + } + views.setImageViewBitmap(R.id.appwidget_image, bitmap) + appWidgetManager.updateAppWidget(appWidgetId, views) + } + + override fun onFailureImpl(dataSource: DataSource>) { + // Ignore failure for now. + } + }, CallerThreadExecutor.getInstance()) + } + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + ApplicationlessInjection + .getInstance(context.applicationContext) + .commonsApplicationComponent + .inject(this) + + // There may be multiple widgets active, so update all of them + for (appWidgetId in appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId) + } + } + + override fun onEnabled(context: Context) { + // Enter relevant functionality for when the first widget is created + } + + override fun onDisabled(context: Context) { + // Enter relevant functionality for when the last widget is disabled + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/widget/ViewHolder.java b/app/src/main/java/fr/free/nrw/commons/widget/ViewHolder.java deleted file mode 100644 index e2dd8d680e..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/widget/ViewHolder.java +++ /dev/null @@ -1,7 +0,0 @@ -package fr.free.nrw.commons.widget; - -import android.content.Context; - -public interface ViewHolder { - void bindModel(Context context, T model); -} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/widget/ViewHolder.kt b/app/src/main/java/fr/free/nrw/commons/widget/ViewHolder.kt new file mode 100644 index 0000000000..f9f598b3e9 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/widget/ViewHolder.kt @@ -0,0 +1,7 @@ +package fr.free.nrw.commons.widget + +import android.content.Context + +interface ViewHolder { + fun bindModel(context: Context, model: T) +} \ No newline at end of file