more idiomatic kotlin, removed guava
This commit is contained in:
parent
bb96f2f65a
commit
7aa50e7880
7 changed files with 74 additions and 126 deletions
|
@ -100,7 +100,6 @@ dependencies {
|
||||||
implementation 'com.commonsware.cwac:document:0.4.1'
|
implementation 'com.commonsware.cwac:document:0.4.1'
|
||||||
implementation 'com.drewnoakes:metadata-extractor:2.15.0'
|
implementation 'com.drewnoakes:metadata-extractor:2.15.0'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
implementation 'com.google.guava:guava:30.0-android'
|
|
||||||
|
|
||||||
kapt 'androidx.annotation:annotation:1.1.0'
|
kapt 'androidx.annotation:annotation:1.1.0'
|
||||||
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
||||||
|
|
|
@ -11,6 +11,7 @@ import deckers.thibault.aves.model.provider.MediaStoreImageProvider
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
@ -27,9 +28,9 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
||||||
GlobalScope.launch { Glide.get(activity).clearDiskCache() }
|
GlobalScope.launch { Glide.get(activity).clearDiskCache() }
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
"rename" -> GlobalScope.launch { rename(call, Coresult(result)) }
|
"rename" -> GlobalScope.launch(Dispatchers.IO) { rename(call, Coresult(result)) }
|
||||||
"rotate" -> GlobalScope.launch { rotate(call, Coresult(result)) }
|
"rotate" -> GlobalScope.launch(Dispatchers.IO) { rotate(call, Coresult(result)) }
|
||||||
"flip" -> GlobalScope.launch { flip(call, Coresult(result)) }
|
"flip" -> GlobalScope.launch(Dispatchers.IO) { flip(call, Coresult(result)) }
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import com.adobe.internal.xmp.XMPUtils
|
||||||
import com.adobe.internal.xmp.properties.XMPPropertyInfo
|
import com.adobe.internal.xmp.properties.XMPPropertyInfo
|
||||||
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
|
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
|
||||||
import com.drew.imaging.ImageMetadataReader
|
import com.drew.imaging.ImageMetadataReader
|
||||||
import com.drew.imaging.ImageProcessingException
|
|
||||||
import com.drew.lang.Rational
|
import com.drew.lang.Rational
|
||||||
import com.drew.metadata.exif.ExifDirectoryBase
|
import com.drew.metadata.exif.ExifDirectoryBase
|
||||||
import com.drew.metadata.exif.ExifIFD0Directory
|
import com.drew.metadata.exif.ExifIFD0Directory
|
||||||
|
@ -59,7 +58,6 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
@ -568,9 +566,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: Exception) {
|
||||||
Log.w(LOG_TAG, "failed to extract xmp thumbnail", e)
|
|
||||||
} catch (e: ImageProcessingException) {
|
|
||||||
Log.w(LOG_TAG, "failed to extract xmp thumbnail", e)
|
Log.w(LOG_TAG, "failed to extract xmp thumbnail", e)
|
||||||
} catch (e: NoClassDefFoundError) {
|
} catch (e: NoClassDefFoundError) {
|
||||||
Log.w(LOG_TAG, "failed to extract xmp thumbnail", e)
|
Log.w(LOG_TAG, "failed to extract xmp thumbnail", e)
|
||||||
|
|
|
@ -13,10 +13,10 @@ import deckers.thibault.aves.utils.LogUtils.createTag
|
||||||
import deckers.thibault.aves.utils.StorageUtils
|
import deckers.thibault.aves.utils.StorageUtils
|
||||||
import io.flutter.plugin.common.EventChannel
|
import io.flutter.plugin.common.EventChannel
|
||||||
import io.flutter.plugin.common.EventChannel.EventSink
|
import io.flutter.plugin.common.EventChannel.EventSink
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
|
||||||
|
|
||||||
class ImageOpStreamHandler(private val context: Context, private val arguments: Any?) : EventChannel.StreamHandler {
|
class ImageOpStreamHandler(private val context: Context, private val arguments: Any?) : EventChannel.StreamHandler {
|
||||||
private lateinit var eventSink: EventSink
|
private lateinit var eventSink: EventSink
|
||||||
|
@ -41,8 +41,8 @@ class ImageOpStreamHandler(private val context: Context, private val arguments:
|
||||||
handler = Handler(Looper.getMainLooper())
|
handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
when (op) {
|
when (op) {
|
||||||
"delete" -> GlobalScope.launch { delete() }
|
"delete" -> GlobalScope.launch(Dispatchers.IO) { delete() }
|
||||||
"move" -> GlobalScope.launch { move() }
|
"move" -> GlobalScope.launch(Dispatchers.IO) { move() }
|
||||||
else -> endOfStream()
|
else -> endOfStream()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ class ImageOpStreamHandler(private val context: Context, private val arguments:
|
||||||
endOfStream()
|
endOfStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete() {
|
private suspend fun delete() {
|
||||||
if (entryMapList.isEmpty()) {
|
if (entryMapList.isEmpty()) {
|
||||||
endOfStream()
|
endOfStream()
|
||||||
return
|
return
|
||||||
|
@ -114,12 +114,9 @@ class ImageOpStreamHandler(private val context: Context, private val arguments:
|
||||||
"uri" to uri.toString(),
|
"uri" to uri.toString(),
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
provider.delete(context, uri, path).get()
|
provider.delete(context, uri, path)
|
||||||
result["success"] = true
|
result["success"] = true
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: Exception) {
|
||||||
Log.w(LOG_TAG, "failed to delete entry with path=$path", e)
|
|
||||||
result["success"] = false
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
Log.w(LOG_TAG, "failed to delete entry with path=$path", e)
|
Log.w(LOG_TAG, "failed to delete entry with path=$path", e)
|
||||||
result["success"] = false
|
result["success"] = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.commonsware.cwac.document.DocumentFileCompat
|
import com.commonsware.cwac.document.DocumentFileCompat
|
||||||
import com.google.common.util.concurrent.Futures
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
|
||||||
import deckers.thibault.aves.model.AvesImageEntry
|
import deckers.thibault.aves.model.AvesImageEntry
|
||||||
import deckers.thibault.aves.model.ExifOrientationOp
|
import deckers.thibault.aves.model.ExifOrientationOp
|
||||||
import deckers.thibault.aves.utils.LogUtils.createTag
|
import deckers.thibault.aves.utils.LogUtils.createTag
|
||||||
|
@ -30,8 +28,8 @@ abstract class ImageProvider {
|
||||||
callback.onFailure(UnsupportedOperationException())
|
callback.onFailure(UnsupportedOperationException())
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun delete(context: Context, uri: Uri, path: String?): ListenableFuture<Any?> {
|
open suspend fun delete(context: Context, uri: Uri, path: String?) {
|
||||||
return Futures.immediateFailedFuture(UnsupportedOperationException())
|
throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun moveMultiple(context: Context, copy: Boolean, destinationDir: String, entries: List<AvesImageEntry>, callback: ImageOpCallback) {
|
open suspend fun moveMultiple(context: Context, copy: Boolean, destinationDir: String, entries: List<AvesImageEntry>, callback: ImageOpCallback) {
|
||||||
|
@ -49,6 +47,7 @@ abstract class ImageProvider {
|
||||||
|
|
||||||
val df = getDocumentFile(context, oldPath, oldMediaUri)
|
val df = getDocumentFile(context, oldPath, oldMediaUri)
|
||||||
try {
|
try {
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
val renamed = df != null && df.renameTo(newFilename)
|
val renamed = df != null && df.renameTo(newFilename)
|
||||||
if (!renamed) {
|
if (!renamed) {
|
||||||
callback.onFailure(Exception("failed to rename entry at path=$oldPath"))
|
callback.onFailure(Exception("failed to rename entry at path=$oldPath"))
|
||||||
|
|
|
@ -8,8 +8,6 @@ import android.os.Build
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.commonsware.cwac.document.DocumentFileCompat
|
import com.commonsware.cwac.document.DocumentFileCompat
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
|
||||||
import deckers.thibault.aves.model.AvesImageEntry
|
import deckers.thibault.aves.model.AvesImageEntry
|
||||||
import deckers.thibault.aves.model.SourceImageEntry
|
import deckers.thibault.aves.model.SourceImageEntry
|
||||||
import deckers.thibault.aves.utils.LogUtils.createTag
|
import deckers.thibault.aves.utils.LogUtils.createTag
|
||||||
|
@ -22,9 +20,7 @@ import deckers.thibault.aves.utils.StorageUtils.getDocumentFile
|
||||||
import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission
|
import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
|
||||||
|
|
||||||
class MediaStoreImageProvider : ImageProvider() {
|
class MediaStoreImageProvider : ImageProvider() {
|
||||||
suspend fun fetchAll(context: Context, knownEntries: Map<Int, Int?>, handleNewEntry: NewEntryHandler) {
|
suspend fun fetchAll(context: Context, knownEntries: Map<Int, Int?>, handleNewEntry: NewEntryHandler) {
|
||||||
|
@ -176,41 +172,21 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
private fun needSize(mimeType: String) = MimeTypes.SVG != mimeType
|
private fun needSize(mimeType: String) = MimeTypes.SVG != mimeType
|
||||||
|
|
||||||
// `uri` is a media URI, not a document URI
|
// `uri` is a media URI, not a document URI
|
||||||
override fun delete(context: Context, uri: Uri, path: String?): ListenableFuture<Any?> {
|
override suspend fun delete(context: Context, uri: Uri, path: String?) {
|
||||||
val future = SettableFuture.create<Any?>()
|
path ?: throw Exception("failed to delete file because path is null")
|
||||||
|
|
||||||
if (path == null) {
|
|
||||||
future.setException(Exception("failed to delete file because path is null"))
|
|
||||||
return future
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requireAccessPermission(context, path)) {
|
if (requireAccessPermission(context, path)) {
|
||||||
// if the file is on SD card, calling the content resolver `delete()` removes the entry from the Media Store
|
// if the file is on SD card, calling the content resolver `delete()` removes the entry from the Media Store
|
||||||
// but it doesn't delete the file, even if the app has the permission
|
// but it doesn't delete the file, even if the app has the permission
|
||||||
try {
|
val df = getDocumentFile(context, path, uri)
|
||||||
val df = getDocumentFile(context, path, uri)
|
|
||||||
if (df != null && df.delete()) {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
future.set(null)
|
if (df != null && df.delete()) return
|
||||||
} else {
|
throw Exception("failed to delete file with df=$df")
|
||||||
future.setException(Exception("failed to delete file with df=$df"))
|
|
||||||
}
|
|
||||||
} catch (e: FileNotFoundException) {
|
|
||||||
future.setException(e)
|
|
||||||
}
|
|
||||||
return future
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (context.contentResolver.delete(uri, null, null) > 0) return
|
||||||
if (context.contentResolver.delete(uri, null, null) > 0) {
|
throw Exception("failed to delete row from content provider")
|
||||||
future.set(null)
|
|
||||||
} else {
|
|
||||||
future.setException(Exception("failed to delete row from content provider"))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(LOG_TAG, "failed to delete entry", e)
|
|
||||||
future.setException(e)
|
|
||||||
}
|
|
||||||
return future
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun moveMultiple(
|
override suspend fun moveMultiple(
|
||||||
|
@ -252,14 +228,12 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
// - there is no documentation regarding support for usage with removable storage
|
// - there is no documentation regarding support for usage with removable storage
|
||||||
// - the Media Store only allows inserting in specific primary directories ("DCIM", "Pictures") when using scoped storage
|
// - the Media Store only allows inserting in specific primary directories ("DCIM", "Pictures") when using scoped storage
|
||||||
try {
|
try {
|
||||||
val newFieldsFuture = moveSingleByTreeDocAndScan(
|
val newFields = moveSingleByTreeDocAndScan(
|
||||||
context, sourcePath, sourceUri, destinationDir, destinationDirDocFile, mimeType, copy,
|
context, sourcePath, sourceUri, destinationDir, destinationDirDocFile, mimeType, copy,
|
||||||
)
|
)
|
||||||
result["newFields"] = newFieldsFuture.get()
|
result["newFields"] = newFields
|
||||||
result["success"] = true
|
result["success"] = true
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: Exception) {
|
||||||
Log.w(LOG_TAG, "failed to move to destinationDir=$destinationDir entry with sourcePath=$sourcePath", e)
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
Log.w(LOG_TAG, "failed to move to destinationDir=$destinationDir entry with sourcePath=$sourcePath", e)
|
Log.w(LOG_TAG, "failed to move to destinationDir=$destinationDir entry with sourcePath=$sourcePath", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,68 +249,56 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
destinationDirDocFile: DocumentFileCompat,
|
destinationDirDocFile: DocumentFileCompat,
|
||||||
mimeType: String,
|
mimeType: String,
|
||||||
copy: Boolean,
|
copy: Boolean,
|
||||||
): ListenableFuture<FieldMap> {
|
): FieldMap {
|
||||||
val future = SettableFuture.create<FieldMap>()
|
val sourceFile = File(sourcePath)
|
||||||
|
val sourceDir = sourceFile.parent?.let { ensureTrailingSeparator(it) }
|
||||||
try {
|
if (sourceDir == destinationDir) {
|
||||||
val sourceFile = File(sourcePath)
|
if (copy) throw Exception("file at path=$sourcePath is already in destination directory")
|
||||||
val sourceDir = sourceFile.parent?.let { ensureTrailingSeparator(it) }
|
return HashMap<String, Any?>()
|
||||||
if (sourceDir == destinationDir) {
|
|
||||||
if (copy) {
|
|
||||||
future.setException(Exception("file at path=$sourcePath is already in destination directory"))
|
|
||||||
} else {
|
|
||||||
future.set(HashMap<String, Any?>())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val sourceFileName = sourceFile.name
|
|
||||||
val desiredNameWithoutExtension = sourceFileName.replaceFirst("[.][^.]+$".toRegex(), "")
|
|
||||||
|
|
||||||
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
|
|
||||||
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
|
|
||||||
// through a document URI, not a tree URI
|
|
||||||
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
|
|
||||||
val destinationTreeFile = destinationDirDocFile.createFile(mimeType, desiredNameWithoutExtension)
|
|
||||||
val destinationDocFile = DocumentFileCompat.fromSingleUri(context, destinationTreeFile.uri)
|
|
||||||
|
|
||||||
// `DocumentsContract.moveDocument()` needs `sourceParentDocumentUri`, which could be different for each entry
|
|
||||||
// `DocumentsContract.copyDocument()` yields "Unsupported call: android:copyDocument"
|
|
||||||
// when used with entry URI as `sourceDocumentUri`, and destinationDirDocFile URI as `targetParentDocumentUri`
|
|
||||||
val source = DocumentFileCompat.fromSingleUri(context, sourceUri)
|
|
||||||
source.copyTo(destinationDocFile)
|
|
||||||
|
|
||||||
// the source file name and the created document file name can be different when:
|
|
||||||
// - a file with the same name already exists, so the name gets a suffix like ` (1)`
|
|
||||||
// - the original extension does not match the extension added by the underlying provider
|
|
||||||
val fileName = destinationDocFile.name
|
|
||||||
val destinationFullPath = destinationDir + fileName
|
|
||||||
|
|
||||||
var deletedSource = false
|
|
||||||
if (!copy) {
|
|
||||||
// delete original entry
|
|
||||||
try {
|
|
||||||
delete(context, sourceUri, sourcePath).get()
|
|
||||||
deletedSource = true
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
Log.w(LOG_TAG, "failed to delete entry with path=$sourcePath", e)
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
Log.w(LOG_TAG, "failed to delete entry with path=$sourcePath", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val fields = scanNewPath(context, destinationFullPath, mimeType)
|
|
||||||
fields["deletedSource"] = deletedSource
|
|
||||||
future.set(fields)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
future.setException(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(LOG_TAG, "failed to ${(if (copy) "copy" else "move")} entry", e)
|
|
||||||
future.setException(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return future
|
val sourceFileName = sourceFile.name
|
||||||
|
val desiredNameWithoutExtension = sourceFileName.replaceFirst("[.][^.]+$".toRegex(), "")
|
||||||
|
|
||||||
|
if (File(destinationDir, sourceFileName).exists()) {
|
||||||
|
throw Exception("file with name=$sourceFileName already exists in destination directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
|
||||||
|
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
|
||||||
|
// through a document URI, not a tree URI
|
||||||
|
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
val destinationTreeFile = destinationDirDocFile.createFile(mimeType, desiredNameWithoutExtension)
|
||||||
|
val destinationDocFile = DocumentFileCompat.fromSingleUri(context, destinationTreeFile.uri)
|
||||||
|
|
||||||
|
// `DocumentsContract.moveDocument()` needs `sourceParentDocumentUri`, which could be different for each entry
|
||||||
|
// `DocumentsContract.copyDocument()` yields "Unsupported call: android:copyDocument"
|
||||||
|
// when used with entry URI as `sourceDocumentUri`, and destinationDirDocFile URI as `targetParentDocumentUri`
|
||||||
|
val source = DocumentFileCompat.fromSingleUri(context, sourceUri)
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
source.copyTo(destinationDocFile)
|
||||||
|
|
||||||
|
// the source file name and the created document file name can be different when:
|
||||||
|
// - a file with the same name already exists, some implementations give a suffix like ` (1)`, some *do not*
|
||||||
|
// - the original extension does not match the extension added by the underlying provider
|
||||||
|
val fileName = destinationDocFile.name
|
||||||
|
val destinationFullPath = destinationDir + fileName
|
||||||
|
|
||||||
|
var deletedSource = false
|
||||||
|
if (!copy) {
|
||||||
|
// delete original entry
|
||||||
|
try {
|
||||||
|
delete(context, sourceUri, sourcePath)
|
||||||
|
deletedSource = true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(LOG_TAG, "failed to delete entry with path=$sourcePath", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanNewPath(context, destinationFullPath, mimeType).apply {
|
||||||
|
put("deletedSource", deletedSource)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -37,12 +37,6 @@ class Constants {
|
||||||
licenseUrl: 'https://github.com/bumptech/glide/blob/master/LICENSE',
|
licenseUrl: 'https://github.com/bumptech/glide/blob/master/LICENSE',
|
||||||
sourceUrl: 'https://github.com/bumptech/glide',
|
sourceUrl: 'https://github.com/bumptech/glide',
|
||||||
),
|
),
|
||||||
Dependency(
|
|
||||||
name: 'Guava',
|
|
||||||
license: 'Apache 2.0',
|
|
||||||
licenseUrl: 'https://github.com/google/guava/blob/master/COPYING',
|
|
||||||
sourceUrl: 'https://github.com/google/guava',
|
|
||||||
),
|
|
||||||
Dependency(
|
Dependency(
|
||||||
name: 'Metadata Extractor',
|
name: 'Metadata Extractor',
|
||||||
license: 'Apache 2.0',
|
license: 'Apache 2.0',
|
||||||
|
|
Loading…
Reference in a new issue