diff --git a/app/build.gradle b/app/build.gradle
index c8124e42be..a9c0c498cf 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -51,6 +51,21 @@ dependencies {
implementation 'com.karumi:dexter:5.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+ // Jetpack Compose
+ def composeBom = platform('androidx.compose:compose-bom:2024.08.00')
+
+ implementation "androidx.activity:activity-compose:1.9.1"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.4"
+ implementation (composeBom)
+ implementation "androidx.compose.runtime:runtime"
+ implementation "androidx.compose.ui:ui"
+ implementation "androidx.compose.ui:ui-graphics"
+ implementation "androidx.compose.ui:ui-tooling"
+ implementation "androidx.compose.foundation:foundation"
+ implementation "androidx.compose.foundation:foundation-layout"
+ implementation "androidx.compose.material3:material3"
+ androidTestImplementation(composeBom)
+
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:$ADAPTER_DELEGATES_VERSION"
implementation "com.hannesdorfmann:adapterdelegates4-pagination:$ADAPTER_DELEGATES_VERSION"
implementation "androidx.paging:paging-runtime-ktx:$PAGING_VERSION"
@@ -186,7 +201,7 @@ project.gradle.taskGraph.whenReady {
}
android {
- compileSdkVersion 33
+ compileSdkVersion 34
defaultConfig {
//applicationId 'fr.free.nrw.commons'
@@ -196,7 +211,7 @@ android {
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion 21
- targetSdkVersion 33
+ targetSdkVersion 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
@@ -253,11 +268,12 @@ android {
}
}
debug {
- testCoverageEnabled true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
testProguardFile 'test-proguard-rules.txt'
versionNameSuffix "-debug-" + getBranchName()
+ enableUnitTestCoverage true
+ enableAndroidTestCoverage true
}
}
@@ -354,13 +370,17 @@ android {
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
- jvmTarget = "1.8"
+ jvmTarget = "11"
}
buildToolsVersion buildToolsVersion
buildFeatures {
viewBinding true
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.3.2'
}
namespace 'fr.free.nrw.commons'
lint {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 87e182cc32..6a47a46447 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,23 +3,29 @@
xmlns:tools="http://schemas.android.com/tools">
-
+
-
+
-
-
+
+
-
+
+
+
@@ -183,6 +189,10 @@
android:name="org.acra.sender.SenderService"
android:exported="false"
android:process=":acra" />
+
+
= VERSION_CODES.Q) {
- PermissionUtils.checkPermissionsAndPerformAction(
- this,
- () -> {
- },
- R.string.media_location_permission_denied,
- R.string.add_location_manually,
- permission.ACCESS_MEDIA_LOCATION);
- }
+// if (VERSION.SDK_INT >= VERSION_CODES.Q) {
+// ActivityCompat.requestPermissions(this,
+// new String[]{Manifest.permission.ACCESS_MEDIA_LOCATION}, 0);
+// PermissionUtils.checkPermissionsAndPerformAction(
+// this,
+// () -> {},
+// R.string.media_location_permission_denied,
+// R.string.add_location_manually,
+// permission.ACCESS_MEDIA_LOCATION);
+// }
checkAndResumeStuckUploads();
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt
index f454a3af8c..961d511583 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt
@@ -40,14 +40,14 @@ open class OnSwipeTouchListener(context: Context?) : View.OnTouchListener {
* Detects the gestures
*/
override fun onFling(
- event1: MotionEvent,
+ event1: MotionEvent?,
event2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
try {
- val diffY: Float = event2.y - event1.y
- val diffX: Float = event2.x - event1.x
+ val diffY: Float = event2.y - (event1?.y ?: event2.y)
+ val diffX: Float = event2.x - (event1?.x ?: event2.x)
if (abs(diffX) > abs(diffY)) {
if (abs(diffX) > SWIPE_THRESHOLD_WIDTH && abs(velocityX) >
SWIPE_VELOCITY_THRESHOLD) {
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
index 60d2994917..abebc89444 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
@@ -62,13 +62,15 @@ class FolderAdapter(
folder.images.removeAll(toBeRemoved)
val count = folder.images.size
- if(count == 0) {
+ if(count == 0 && folders.size > 0) {
// Folder is empty, remove folder from the adapter.
holder.itemView.post{
val updatePosition = folders.indexOf(folder)
- folders.removeAt(updatePosition)
- notifyItemRemoved(updatePosition)
- notifyItemRangeChanged(updatePosition, folders.size)
+ if(updatePosition != -1) {
+ folders.removeAt(updatePosition)
+ notifyItemRemoved(updatePosition)
+ notifyItemRangeChanged(updatePosition, folders.size)
+ }
}
} else {
val previewImage = folder.images[0]
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
index 47784153ec..58f4c83850 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
@@ -122,7 +122,7 @@ class ImageAdapter(
* Bind View holder, load image, selected view, click listeners.
*/
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
-
+ if(images.size == 0) { return }
var image=images[position]
holder.image.setImageDrawable (null)
if (context.contentResolver.getType(image.uri) == null) {
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
index 7cf0229cb2..bcb7446d8e 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
@@ -1,16 +1,49 @@
package fr.free.nrw.commons.customselector.ui.selector
+import android.Manifest
import android.app.Activity
import android.app.Dialog
import android.content.Intent
import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
+import android.util.Log
import android.view.View
import android.view.Window
import android.widget.Button
import android.widget.ImageButton
import android.widget.TextView
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedCard
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.database.NotForUploadStatus
@@ -24,10 +57,12 @@ import fr.free.nrw.commons.databinding.ActivityCustomSelectorBinding
import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding
import fr.free.nrw.commons.databinding.CustomSelectorToolbarBinding
import fr.free.nrw.commons.filepicker.Constants
+import fr.free.nrw.commons.filepicker.FilePicker
import fr.free.nrw.commons.media.ZoomableActivity
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.FileUtilsWrapper
import fr.free.nrw.commons.utils.CustomSelectorUtils
+import fr.free.nrw.commons.utils.PermissionUtils
import kotlinx.coroutines.*
import java.io.File
import java.lang.Integer.max
@@ -114,14 +149,37 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
private var progressDialogText:String=""
+ private var showPartialAccessIndicator by mutableStateOf(false)
+
/**
* onCreate Activity, sets theme, initialises the view model, setup view.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
+ ContextCompat.checkSelfPermission(
+ this, Manifest.permission.READ_MEDIA_IMAGES
+ ) == PackageManager.PERMISSION_DENIED
+ ) {
+ showPartialAccessIndicator = true
+ }
+
binding = ActivityCustomSelectorBinding.inflate(layoutInflater)
toolbarBinding = CustomSelectorToolbarBinding.bind(binding.root)
bottomSheetBinding = CustomSelectorBottomLayoutBinding.bind(binding.root)
+ binding.partialAccessIndicator.setContent {
+ PartialStorageAccessIndicator(
+ isVisible = showPartialAccessIndicator,
+ onManage = {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ requestPermissions(arrayOf(Manifest.permission.READ_MEDIA_IMAGES), 1)
+ }
+ },
+ modifier = Modifier
+ .padding(vertical = 8.dp, horizontal = 4.dp)
+ .fillMaxWidth()
+ )
+ }
val view = binding.root
setContentView(view)
@@ -147,6 +205,24 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
}
}
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if(requestCode == 1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ if(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ showPartialAccessIndicator = false
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ fetchData()
+ }
+
/**
* When data will be send from full screen mode, it will be passed to fragment
*/
@@ -181,7 +257,6 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, FolderFragment.newInstance())
.commit()
- fetchData()
setUpToolbar()
setUpBottomLayout()
}
@@ -498,3 +573,52 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
const val ITEM_ID: String = "ItemId"
}
}
+@Composable
+fun PartialStorageAccessIndicator(
+ isVisible: Boolean,
+ onManage: ()-> Unit,
+ modifier: Modifier = Modifier
+) {
+ if(isVisible) {
+ OutlinedCard(
+ modifier = modifier,
+ colors = CardDefaults.cardColors(
+ containerColor = colorResource(R.color.primarySuperLightColor)
+ ),
+ border = BorderStroke(0.5.dp, color = colorResource(R.color.primaryColor)),
+ shape = RoundedCornerShape(8.dp)
+ ) {
+ Row(modifier = Modifier.padding(16.dp).fillMaxWidth()) {
+ Text(
+ text = "You've given access to a select number of photos",
+ modifier = Modifier.weight(1f)
+ )
+ TextButton(
+ onClick = onManage,
+ modifier = Modifier.align(Alignment.Bottom),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = colorResource(R.color.primaryColor)
+ ),
+ shape = RoundedCornerShape(8.dp)
+ ) {
+ Text(
+ text = "Manage",
+ style = MaterialTheme.typography.labelMedium,
+ color = colorResource(R.color.primaryTextColor)
+ )
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PartialStorageAccessIndicatorPreview() {
+ Surface {
+ PartialStorageAccessIndicator(isVisible = true, onManage = {}, modifier = Modifier
+ .padding(vertical = 8.dp, horizontal = 4.dp)
+ .fillMaxWidth()
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
index 0f546e7881..95f427f49e 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -116,11 +116,14 @@ class FolderFragment : CommonsDaggerSupportFragment() {
private fun handleResult(result: Result) {
if(result.status is CallbackStatus.SUCCESS){
val images = result.images
- if(images.isNullOrEmpty())
- {
+ if(images.isEmpty()){
binding?.emptyText?.let {
it.visibility = View.VISIBLE
}
+ } else {
+ binding?.emptyText?.let {
+ it.visibility = View.GONE
+ }
}
folders = ImageHelper.folderListFromImages(result.images)
folderAdapter.init(folders)
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
index 842531dd2c..c5e5de4f64 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
@@ -39,6 +39,7 @@ import fr.free.nrw.commons.upload.FileUtilsWrapper
import io.reactivex.schedulers.Schedulers
import java.util.*
import javax.inject.Inject
+import kotlin.collections.ArrayList
/**
* Custom Selector Image Fragment.
@@ -279,6 +280,8 @@ class ImageFragment : CommonsDaggerSupportFragment(), RefreshUIListener, PassDat
}
}
} else {
+ filteredImages = ArrayList()
+ allImages = filteredImages
binding?.emptyText?.let {
it.visibility = View.VISIBLE
}
@@ -324,7 +327,7 @@ class ImageFragment : CommonsDaggerSupportFragment(), RefreshUIListener, PassDat
.findFirstVisibleItemPosition()
// Check for empty RecyclerView.
- if (position != -1) {
+ if (position != -1 && filteredImages.size > 0) {
context?.let { context ->
context.getSharedPreferences(
"CustomSelector",
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
index d4c08d7a53..b63d3a4c10 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationHelper.java
@@ -4,16 +4,12 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-
import android.os.Build;
import androidx.core.app.NotificationCompat;
-
import javax.inject.Inject;
import javax.inject.Singleton;
-
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
-
import static androidx.core.app.NotificationCompat.DEFAULT_ALL;
import static androidx.core.app.NotificationCompat.PRIORITY_HIGH;
@@ -30,11 +26,11 @@ public class NotificationHelper {
public static final int NOTIFICATION_EDIT_DESCRIPTION = 4;
public static final int NOTIFICATION_EDIT_DEPICTIONS = 5;
- private NotificationManager notificationManager;
- private NotificationCompat.Builder notificationBuilder;
+ private final NotificationManager notificationManager;
+ private final NotificationCompat.Builder notificationBuilder;
@Inject
- public NotificationHelper(Context context) {
+ public NotificationHelper(final Context context) {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationBuilder = new NotificationCompat
.Builder(context, CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL)
@@ -49,12 +45,13 @@ public NotificationHelper(Context context) {
* @param notificationId the notificationID
* @param intent the intent to be fired when the notification is clicked
*/
- public void showNotification(Context context,
- String notificationTitle,
- String notificationMessage,
- int notificationId,
- Intent intent) {
-
+ public void showNotification(
+ final Context context,
+ final String notificationTitle,
+ final String notificationMessage,
+ final int notificationId,
+ final Intent intent
+ ) {
notificationBuilder.setDefaults(DEFAULT_ALL)
.setContentTitle(notificationTitle)
.setStyle(new NotificationCompat.BigTextStyle()
@@ -65,14 +62,11 @@ public void showNotification(Context context,
.setPriority(PRIORITY_HIGH);
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
-
- // Check if the API level is 31 or higher to modify the flag
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- // For API level 31 or above, PendingIntent requires either FLAG_IMMUTABLE or FLAG_MUTABLE to be set
- flags |= PendingIntent.FLAG_IMMUTABLE;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ flags |= PendingIntent.FLAG_IMMUTABLE; // This flag was introduced in API 23
}
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, flags);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, flags);
notificationBuilder.setContentIntent(pendingIntent);
notificationManager.notify(notificationId, notificationBuilder.build());
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
index 707bf13639..eb180ec44b 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
@@ -278,7 +278,8 @@ protected void checkBlockStatus() {
public void checkStoragePermissions() {
// Check if all required permissions are granted
final boolean hasAllPermissions = PermissionUtils.hasPermission(this, PERMISSIONS_STORAGE);
- if (hasAllPermissions) {
+ final boolean hasPartialAccess = PermissionUtils.hasPartialAccess(this);
+ if (hasAllPermissions || hasPartialAccess) {
// All required permissions are granted, so enable UI elements and perform actions
receiveSharedItems();
binding.cvContainerTopCard.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
index 4a06cafb61..ebf930915e 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
@@ -6,6 +6,7 @@ import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.content.Context
import android.content.Intent
+import android.content.pm.ServiceInfo
import android.graphics.BitmapFactory
import android.os.Build
import androidx.core.app.NotificationCompat
@@ -46,13 +47,12 @@ import java.util.*
import java.util.regex.Pattern
import javax.inject.Inject
-
-class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
- CoroutineWorker(appContext, workerParams) {
+class UploadWorker(
+ private var appContext: Context, workerParams: WorkerParameters
+): CoroutineWorker(appContext, workerParams) {
private var notificationManager: NotificationManagerCompat? = null
-
@Inject
lateinit var wikidataEditService: WikidataEditService
@@ -83,12 +83,11 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
//Attributes of the current-upload notification
private var currentNotificationID: Int = -1// lateinit is not allowed with primitives
private lateinit var currentNotificationTag: String
- private var curentNotification: NotificationCompat.Builder
+ private var currentNotification: NotificationCompat.Builder
private val statesToProcess= ArrayList()
- private val STASH_ERROR_CODES = Arrays
- .asList(
+ private val STASH_ERROR_CODES = listOf(
"uploadstash-file-not-found",
"stashfailed",
"verification-error",
@@ -100,7 +99,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
.getInstance(appContext)
.commonsApplicationComponent
.inject(this)
- curentNotification =
+ currentNotification =
getNotificationBuilder(CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL)!!
statesToProcess.add(Contribution.STATE_QUEUED)
@@ -120,21 +119,23 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
fun onProgress(transferred: Long, total: Long) {
if (transferred == total) {
// Completed!
- curentNotification.setContentTitle(notificationFinishingTitle)
+ currentNotification.setContentTitle(notificationFinishingTitle)
.setProgress(0, 100, true)
} else {
- curentNotification
+ currentNotification
.setProgress(
100,
(transferred.toDouble() / total.toDouble() * 100).toInt(),
false
)
}
- notificationManager?.cancel(PROCESSING_UPLOADS_NOTIFICATION_TAG, PROCESSING_UPLOADS_NOTIFICATION_ID)
+ notificationManager?.cancel(
+ PROCESSING_UPLOADS_NOTIFICATION_TAG, PROCESSING_UPLOADS_NOTIFICATION_ID
+ )
notificationManager?.notify(
currentNotificationTag,
currentNotificationID,
- curentNotification.build()!!
+ currentNotification.build()
)
contribution!!.transferred = transferred
contributionDao.update(contribution).blockingAwait()
@@ -248,10 +249,18 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
* Create new notification for foreground service
*/
private fun createForegroundInfo(): ForegroundInfo {
- return ForegroundInfo(
- 1,
- createNotificationForForegroundService()
- )
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ ForegroundInfo(
+ 1,
+ createNotificationForForegroundService(),
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ )
+ } else {
+ ForegroundInfo(
+ 1,
+ createNotificationForForegroundService()
+ )
+ }
}
override suspend fun getForegroundInfo(): ForegroundInfo {
@@ -282,9 +291,9 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
currentNotificationID =
(contribution.localUri.toString() + contribution.media.filename).hashCode()
- curentNotification
+ currentNotification
getNotificationBuilder(CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL)!!
- curentNotification.setContentTitle(
+ currentNotification.setContentTitle(
appContext.getString(
R.string.upload_progress_notification_title_start,
displayTitle
@@ -294,7 +303,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
notificationManager?.notify(
currentNotificationTag,
currentNotificationID,
- curentNotification.build()!!
+ currentNotification.build()
)
val filename = media.filename
@@ -312,14 +321,16 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
val stashUploadResult = uploadClient.uploadFileToStash(
filename!!, contribution, notificationProgressUpdater
).onErrorReturn{
- return@onErrorReturn StashUploadResult(StashUploadState.FAILED,fileKey = null,errorMessage = it.message)
+ return@onErrorReturn StashUploadResult(
+ StashUploadState.FAILED,fileKey = null,errorMessage = it.message
+ )
}.blockingSingle()
when (stashUploadResult.state) {
StashUploadState.SUCCESS -> {
//If the stash upload succeeds
Timber.d("Upload to stash success for fileName: $filename")
- Timber.d("Ensure uniqueness of filename");
- val uniqueFileName = findUniqueFileName(filename!!)
+ Timber.d("Ensure uniqueness of filename")
+ val uniqueFileName = findUniqueFileName(filename)
try {
//Upload the file from stash
@@ -335,7 +346,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
)
wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution)
- .blockingSubscribe();
+ .blockingSubscribe()
if(contribution.wikidataPlace==null){
Timber.d(
"WikiDataEdit not required, upload success"
@@ -378,12 +389,15 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
}
else -> {
Timber.e("""upload file to stash failed with status: ${stashUploadResult.state}""")
+
contribution.state = Contribution.STATE_FAILED
contribution.chunkInfo = null
contribution.errorInfo = stashUploadResult.errorMessage
showErrorNotification(contribution)
contributionDao.saveSynchronous(contribution)
- if (stashUploadResult.errorMessage.equals(CsrfTokenClient.INVALID_TOKEN_ERROR_MESSAGE)) {
+ if (stashUploadResult.errorMessage.equals(
+ CsrfTokenClient.INVALID_TOKEN_ERROR_MESSAGE)
+ ) {
Timber.e("Invalid Login, logging out")
showInvalidLoginNotification(contribution)
val username = sessionManager.userName
@@ -475,7 +489,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
imageSha1 == modifiedSha1,
true
)
- );
+ )
}
}
}
@@ -519,8 +533,8 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
private fun showSuccessNotification(contribution: Contribution) {
val displayTitle = contribution.media.displayTitle
contribution.state=Contribution.STATE_COMPLETED
- curentNotification.setContentIntent(getPendingIntent(MainActivity::class.java))
- curentNotification.setContentTitle(
+ currentNotification.setContentIntent(getPendingIntent(MainActivity::class.java))
+ currentNotification.setContentTitle(
appContext.getString(
R.string.upload_completed_notification_title,
displayTitle
@@ -531,7 +545,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
.setOngoing(false)
notificationManager?.notify(
currentNotificationTag, currentNotificationID,
- curentNotification.build()
+ currentNotification.build()
)
}
@@ -542,8 +556,8 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
@SuppressLint("StringFormatInvalid")
private fun showFailedNotification(contribution: Contribution) {
val displayTitle = contribution.media.displayTitle
- curentNotification.setContentIntent(getPendingIntent(UploadProgressActivity::class.java))
- curentNotification.setContentTitle(
+ currentNotification.setContentIntent(getPendingIntent(UploadProgressActivity::class.java))
+ currentNotification.setContentTitle(
appContext.getString(
R.string.upload_failed_notification_title,
displayTitle
@@ -554,13 +568,13 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
.setOngoing(false)
notificationManager?.notify(
currentNotificationTag, currentNotificationID,
- curentNotification.build()
+ currentNotification.build()
)
}
@SuppressLint("StringFormatInvalid")
private fun showInvalidLoginNotification(contribution: Contribution) {
val displayTitle = contribution.media.displayTitle
- curentNotification.setContentTitle(
+ currentNotification.setContentTitle(
appContext.getString(
R.string.upload_failed_notification_title,
displayTitle
@@ -571,7 +585,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
.setOngoing(false)
notificationManager?.notify(
currentNotificationTag, currentNotificationID,
- curentNotification.build()
+ currentNotification.build()
)
}
@@ -581,7 +595,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
@SuppressLint("StringFormatInvalid")
private fun showErrorNotification(contribution: Contribution) {
val displayTitle = contribution.media.displayTitle
- curentNotification.setContentTitle(
+ currentNotification.setContentTitle(
appContext.getString(
R.string.upload_failed_notification_title,
displayTitle
@@ -592,7 +606,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
.setOngoing(false)
notificationManager?.notify(
currentNotificationTag, currentNotificationID,
- curentNotification.build()
+ currentNotification.build()
)
}
@@ -602,8 +616,9 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
*/
private fun showPausedNotification(contribution: Contribution) {
val displayTitle = contribution.media.displayTitle
- curentNotification.setContentIntent(getPendingIntent(UploadProgressActivity::class.java))
- curentNotification.setContentTitle(
+
+ currentNotification.setContentIntent(getPendingIntent(UploadProgressActivity::class.java))
+ currentNotification.setContentTitle(
appContext.getString(
R.string.upload_paused_notification_title,
displayTitle
@@ -614,7 +629,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
.setOngoing(false)
notificationManager!!.notify(
currentNotificationTag, currentNotificationID,
- curentNotification.build()
+ currentNotification.build()
)
}
@@ -624,8 +639,8 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
*/
private fun showCancelledNotification(contribution: Contribution) {
val displayTitle = contribution.media.displayTitle
- curentNotification.setContentIntent(getPendingIntent(UploadProgressActivity::class.java))
- curentNotification.setContentTitle(
+ currentNotification.setContentIntent(getPendingIntent(UploadProgressActivity::class.java))
+ currentNotification.setContentTitle(
displayTitle
)
.setContentText("Upload has been cancelled!")
@@ -633,7 +648,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
.setOngoing(false)
notificationManager!!.notify(
currentNotificationTag, currentNotificationID,
- curentNotification.build()
+ currentNotification.build()
)
}
@@ -652,6 +667,6 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
} else {
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
- };
+ }
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java
index b0a72eae16..9082c1f0fc 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.utils;
import android.Manifest;
+import android.Manifest.permission;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -20,24 +21,26 @@
import fr.free.nrw.commons.upload.UploadActivity;
import java.util.List;
-
public class PermissionUtils {
+ public static String[] PERMISSIONS_STORAGE = getPermissionsStorage();
- public static String[] PERMISSIONS_STORAGE = isSDKVersionScopedStorageCompatible() ?
- isSDKVersionTiramisu() ? new String[]{
- Manifest.permission.READ_MEDIA_IMAGES} :
- new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}
- : isSDKVersionTiramisu() ? new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_MEDIA_IMAGES}
- : new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_EXTERNAL_STORAGE};
-
- private static boolean isSDKVersionScopedStorageCompatible() {
- return Build.VERSION.SDK_INT > Build.VERSION_CODES.P;
- }
-
- public static boolean isSDKVersionTiramisu() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
+ static String[] getPermissionsStorage() {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return new String[]{ Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED,
+ Manifest.permission.READ_MEDIA_IMAGES,
+ Manifest.permission.ACCESS_MEDIA_LOCATION };
+ }
+ if(Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
+ return new String[]{ Manifest.permission.READ_MEDIA_IMAGES,
+ Manifest. permission.ACCESS_MEDIA_LOCATION };
+ }
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ return new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.ACCESS_MEDIA_LOCATION };
+ }
+ return new String[]{
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE };
}
/**
@@ -45,11 +48,11 @@ public static boolean isSDKVersionTiramisu() {
* blocked(marked never ask again by the user) It open the app settings from where the user can
* manually give us the required permission.
*
- * @param activity
+ * @param activity The Activity which requires a permission which has been blocked
*/
- private static void askUserToManuallyEnablePermissionFromSettings(Activity activity) {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
+ private static void askUserToManuallyEnablePermissionFromSettings(final Activity activity) {
+ final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ final Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivityForResult(intent,
CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS);
@@ -58,14 +61,13 @@ private static void askUserToManuallyEnablePermissionFromSettings(Activity activ
/**
* Checks whether the app already has a particular permission
*
- * @param activity
- * @param permissions permissions to be checked
- * @return
+ * @param activity The Activity context to check permissions against
+ * @param permissions An array of permission strings to check
+ * @return `true if the app has all the specified permissions, `false` otherwise
*/
- public static boolean hasPermission(Activity activity, String permissions[]) {
+ public static boolean hasPermission(final Activity activity, final String[] permissions) {
boolean hasPermission = true;
- for (String permission : permissions
- ) {
+ for(final String permission : permissions) {
hasPermission = hasPermission &&
ContextCompat.checkSelfPermission(activity, permission)
== PackageManager.PERMISSION_GRANTED;
@@ -73,6 +75,17 @@ public static boolean hasPermission(Activity activity, String permissions[]) {
return hasPermission;
}
+ public static boolean hasPartialAccess(final Activity activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return ContextCompat.checkSelfPermission(activity,
+ permission.READ_MEDIA_VISUAL_USER_SELECTED
+ ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
+ activity, permission.READ_MEDIA_IMAGES
+ ) == PackageManager.PERMISSION_DENIED;
+ }
+ return false;
+ }
+
/**
* Checks for a particular permission and runs the runnable to perform an action when the
* permission is granted Also, it shows a rationale if needed
@@ -99,9 +112,17 @@ public static boolean hasPermission(Activity activity, String permissions[]) {
* @param rationaleMessage rationale message to be displayed when permission was denied. It
* can be an invalid @StringRes
*/
- public static void checkPermissionsAndPerformAction(Activity activity,
- Runnable onPermissionGranted, @StringRes int rationaleTitle,
- @StringRes int rationaleMessage, String... permissions) {
+ public static void checkPermissionsAndPerformAction(
+ final Activity activity,
+ final Runnable onPermissionGranted,
+ final @StringRes int rationaleTitle,
+ final @StringRes int rationaleMessage,
+ final String... permissions
+ ) {
+ if (hasPartialAccess(activity)) {
+ onPermissionGranted.run();
+ return;
+ }
checkPermissionsAndPerformAction(activity, onPermissionGranted, null,
rationaleTitle, rationaleMessage, permissions);
}
@@ -125,25 +146,30 @@ public static void checkPermissionsAndPerformAction(Activity activity,
* @param rationaleTitle rationale title to be displayed when permission was denied
* @param rationaleMessage rationale message to be displayed when permission was denied
*/
- public static void checkPermissionsAndPerformAction(Activity activity,
- Runnable onPermissionGranted, Runnable onPermissionDenied, @StringRes int rationaleTitle,
- @StringRes int rationaleMessage, String... permissions) {
+ public static void checkPermissionsAndPerformAction(
+ final Activity activity,
+ final Runnable onPermissionGranted,
+ final Runnable onPermissionDenied,
+ final @StringRes int rationaleTitle,
+ final @StringRes int rationaleMessage,
+ final String... permissions
+ ) {
Dexter.withActivity(activity)
.withPermissions(permissions)
.withListener(new MultiplePermissionsListener() {
@Override
- public void onPermissionsChecked(MultiplePermissionsReport report) {
- if (report.areAllPermissionsGranted()) {
+ public void onPermissionsChecked(final MultiplePermissionsReport report) {
+ if (report.areAllPermissionsGranted() || hasPartialAccess(activity)) {
onPermissionGranted.run();
return;
}
if (report.isAnyPermissionPermanentlyDenied()) {
// permission is denied permanently, we will show user a dialog message.
- DialogUtil.showAlertDialog(activity, activity.getString(rationaleTitle),
+ DialogUtil.showAlertDialog(
+ activity, activity.getString(rationaleTitle),
activity.getString(rationaleMessage),
activity.getString(R.string.navigation_item_settings),
- null,
- () -> {
+ null, () -> {
askUserToManuallyEnablePermissionFromSettings(activity);
if (activity instanceof UploadActivity) {
((UploadActivity) activity).setShowPermissionsDialog(true);
@@ -158,13 +184,16 @@ public void onPermissionsChecked(MultiplePermissionsReport report) {
}
@Override
- public void onPermissionRationaleShouldBeShown(List permissions,
- PermissionToken token) {
+ public void onPermissionRationaleShouldBeShown(
+ final List permissions,
+ final PermissionToken token
+ ) {
if (rationaleTitle == -1 && rationaleMessage == -1) {
token.continuePermissionRequest();
return;
}
- DialogUtil.showAlertDialog(activity, activity.getString(rationaleTitle),
+ DialogUtil.showAlertDialog(
+ activity, activity.getString(rationaleTitle),
activity.getString(rationaleMessage),
activity.getString(android.R.string.ok),
activity.getString(android.R.string.cancel),
@@ -173,24 +202,19 @@ public void onPermissionRationaleShouldBeShown(List permissio
((UploadActivity) activity).setShowPermissionsDialog(true);
}
token.continuePermissionRequest();
- }
- ,
+ },
() -> {
Toast.makeText(activity.getApplicationContext(),
- R.string.permissions_are_required_for_functionality,
- Toast.LENGTH_LONG)
- .show();
+ R.string.permissions_are_required_for_functionality,
+ Toast.LENGTH_LONG
+ ).show();
token.cancelPermissionRequest();
if (activity instanceof UploadActivity) {
activity.finish();
}
- }
- ,
- null,
- false);
+ }, null, false
+ );
}
- })
- .onSameThread()
- .check();
+ }).onSameThread().check();
}
}
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 f336317847..2734520787 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
@@ -9,10 +9,9 @@
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;
@@ -22,10 +21,8 @@
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;
@@ -41,17 +38,28 @@
*/
public class PicOfDayAppWidget extends AppWidgetProvider {
- private CompositeDisposable compositeDisposable = new CompositeDisposable();
+ private final CompositeDisposable compositeDisposable = new CompositeDisposable();
@Inject
MediaClient mediaClient;
- void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.pic_of_day_app_widget);
+ 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
- Intent viewIntent = new Intent(context, MainActivity.class);
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, viewIntent, PendingIntent.FLAG_IMMUTABLE);
+ 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);
@@ -60,61 +68,76 @@ void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int app
/**
* Loads the picture of the day using media wiki API
- * @param context
- * @param views
- * @param appWidgetManager
- * @param appWidgetId
+ * @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(Context context,
- RemoteViews views,
- AppWidgetManager appWidgetManager,
- int appWidgetId) {
+ 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
- Intent viewIntent = new Intent();
- viewIntent.setAction(ACTION_VIEW);
- viewIntent.setData(Uri.parse(response.getPageTitle().getMobileUri()));
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, viewIntent, PendingIntent.FLAG_IMMUTABLE);
- views.setOnClickPendingIntent(R.id.appwidget_image, pendingIntent);
-
- loadImageFromUrl(response.getThumbUrl(), context, views, appWidgetManager, appWidgetId);
+ 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;
}
- },
- t -> Timber.e(t, "Fetching picture of the day failed")
+ 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
- * @param context
- * @param views
- * @param appWidgetManager
- * @param appWidgetId
+ * @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(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);
+ 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 Bitmap tempBitmap) {
+ protected void onNewResultImpl(@Nullable final Bitmap tempBitmap) {
Bitmap bitmap = null;
if (tempBitmap != null) {
- bitmap = Bitmap.createBitmap(tempBitmap.getWidth(), tempBitmap.getHeight(), Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
+ 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);
@@ -122,32 +145,37 @@ protected void onNewResultImpl(@Nullable Bitmap tempBitmap) {
}
@Override
- protected void onFailureImpl(DataSource> dataSource) {
+ protected void onFailureImpl(
+ final DataSource> dataSource
+ ) {
// Ignore failure for now.
}
}, CallerThreadExecutor.getInstance());
}
@Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ public void onUpdate(
+ final Context context,
+ final AppWidgetManager appWidgetManager,
+ final int[] appWidgetIds
+ ) {
ApplicationlessInjection
- .getInstance(context
- .getApplicationContext())
+ .getInstance(context.getApplicationContext())
.getCommonsApplicationComponent()
.inject(this);
// There may be multiple widgets active, so update all of them
- for (int appWidgetId : appWidgetIds) {
+ for (final int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
- public void onEnabled(Context context) {
+ public void onEnabled(final Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
- public void onDisabled(Context context) {
+ public void onDisabled(final Context context) {
// Enter relevant functionality for when the last widget is disabled
}
}
diff --git a/app/src/main/res/layout/activity_custom_selector.xml b/app/src/main/res/layout/activity_custom_selector.xml
index fbd036f94f..02c8644229 100644
--- a/app/src/main/res/layout/activity_custom_selector.xml
+++ b/app/src/main/res/layout/activity_custom_selector.xml
@@ -1,17 +1,25 @@
+
+
+ app:layout_constraintTop_toBottomOf="@+id/partial_access_indicator"
+ tools:layout_editor_absoluteX="-16dp" />
diff --git a/build.gradle b/build.gradle
index 8e8c8911db..003163cb8f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.0.2'
+ classpath 'com.android.tools.build:gradle:8.5.0'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
classpath 'org.codehaus.groovy:groovy-all:2.4.15'
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d2e417be93..fb6a720531 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Sun Apr 23 18:22:54 IST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
\ No newline at end of file