Migrated widget module from Java to Kotlin (#5940)

* Rename .java to .kt

* Migrated widget module to Kotlin
This commit is contained in:
Saifuddin Adenwala 2024-11-20 09:11:50 +05:30 committed by GitHub
parent 0fdb0044b9
commit cb4ffd8ca8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 224 additions and 237 deletions

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons.di;
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;

View file

@ -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);
}
}

View file

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

View file

@ -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<CloseableReference<CloseableImage>> 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<CloseableReference<CloseableImage>> 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
}
}

View file

@ -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<CloseableReference<CloseableImage>>) {
// 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
}
}

View file

@ -1,7 +0,0 @@
package fr.free.nrw.commons.widget;
import android.content.Context;
public interface ViewHolder<T> {
void bindModel(Context context, T model);
}

View file

@ -0,0 +1,7 @@
package fr.free.nrw.commons.widget
import android.content.Context
interface ViewHolder<T> {
fun bindModel(context: Context, model: T)
}