mirror of
https://github.com/nextcloud/android.git
synced 2024-11-27 09:39:25 +03:00
Add tile view to media view
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
ac20b55d90
commit
66d8756bec
39 changed files with 1152 additions and 308 deletions
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2022 Tobias Kaminsky
|
||||
* Copyright (C) 2022 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.ui.fragment
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||
import com.nextcloud.client.TestActivity
|
||||
import com.owncloud.android.AbstractIT
|
||||
import com.owncloud.android.datamodel.ImageDimension
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE
|
||||
import com.owncloud.android.lib.common.utils.Log_OC
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.Random
|
||||
|
||||
class GalleryFragmentIT : AbstractIT() {
|
||||
@get:Rule
|
||||
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
|
||||
|
||||
lateinit var activity: TestActivity
|
||||
val random = Random()
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
activity = testActivityRule.launchActivity(null)
|
||||
|
||||
createImage(1, true, 700, 300)
|
||||
createImage(2, true, 500, 300)
|
||||
|
||||
// createImage(3, true, 300, 400)
|
||||
// createImage(4, true, 600, 800)
|
||||
//
|
||||
// createImage(5, true, 700, 300)
|
||||
// createImage(6, true, 300, 400)
|
||||
|
||||
createImage(7, true, 300, 400)
|
||||
|
||||
// for (i in 7..50) {
|
||||
// createImage(i)
|
||||
// }
|
||||
}
|
||||
|
||||
@After
|
||||
override fun after() {
|
||||
ThumbnailsCacheManager.clearCache()
|
||||
|
||||
super.after()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showGallery() {
|
||||
val sut = GalleryFragment()
|
||||
activity.addFragment(sut)
|
||||
|
||||
longSleep()
|
||||
}
|
||||
|
||||
private fun createImage(int: Int, createPreview: Boolean = true, width: Int? = null, height: Int? = null) {
|
||||
val defaultSize = ThumbnailsCacheManager.getThumbnailDimension().toFloat()
|
||||
val file = OCFile("/$int.png").apply {
|
||||
fileId = int.toLong()
|
||||
remoteId = "$int"
|
||||
mimeType = "image/png"
|
||||
isPreviewAvailable = true
|
||||
modificationTimestamp = (1658475504 + int.toLong()) * 1000
|
||||
imageDimension = ImageDimension(width?.toFloat() ?: defaultSize, height?.toFloat() ?: defaultSize)
|
||||
storageManager.saveFile(this)
|
||||
}
|
||||
|
||||
if (!createPreview) {
|
||||
return
|
||||
}
|
||||
|
||||
// create dummy thumbnail
|
||||
var w: Int
|
||||
var h: Int
|
||||
if (width == null || height == null) {
|
||||
if (random.nextBoolean()) {
|
||||
// portrait
|
||||
w = (random.nextInt(3) + 2) * 100 // 200-400
|
||||
h = (random.nextInt(5) + 4) * 100 // 400-800
|
||||
} else {
|
||||
// landscape
|
||||
w = (random.nextInt(5) + 4) * 100 // 400-800
|
||||
h = (random.nextInt(3) + 2) * 100 // 200-400
|
||||
}
|
||||
} else {
|
||||
w = width
|
||||
h = height
|
||||
}
|
||||
|
||||
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
Canvas(bitmap).apply {
|
||||
drawRGB(random.nextInt(256), random.nextInt(256), random.nextInt(256))
|
||||
drawCircle(w / 2f, h / 2f, Math.min(w, h) / 2f, Paint().apply { color = Color.BLACK })
|
||||
}
|
||||
ThumbnailsCacheManager.addBitmapToCache(PREFIX_RESIZED_IMAGE + file.remoteId, bitmap)
|
||||
|
||||
assertNotNull(ThumbnailsCacheManager.getBitmapFromDiskCache(PREFIX_RESIZED_IMAGE + file.remoteId))
|
||||
|
||||
Log_OC.d("Gallery_thumbnail", "created $int with ${bitmap.width} x ${bitmap.height}")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2022 Tobias Kaminsky
|
||||
* Copyright (C) 2022 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.utils
|
||||
|
||||
import com.owncloud.android.AbstractIT
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class DisplayUtilsIT : AbstractIT() {
|
||||
@Test
|
||||
fun testPixelToDP() {
|
||||
val px = 123
|
||||
val dp = DisplayUtils.convertPixelToDp(px, targetContext)
|
||||
val newPx = DisplayUtils.convertDpToPixel(dp, targetContext)
|
||||
|
||||
assertEquals(px.toLong(), newPx.toLong())
|
||||
}
|
||||
}
|
|
@ -276,7 +276,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
|
|||
@SuppressFBWarnings("ST")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
enableStrictMode();
|
||||
// enableStrictMode();
|
||||
|
||||
viewThemeUtils = viewThemeUtilsProvider.get();
|
||||
|
||||
|
|
|
@ -509,6 +509,7 @@ public class FileDataStorageManager {
|
|||
cv.put(ProviderTableMeta.FILE_LOCK_TIMEOUT, file.getLockTimeout());
|
||||
cv.put(ProviderTableMeta.FILE_LOCK_TOKEN, file.getLockToken());
|
||||
cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
|
||||
cv.put(ProviderTableMeta.FILE_METADATA_SIZE, new Gson().toJson(file.getImageDimension()));
|
||||
|
||||
return cv;
|
||||
}
|
||||
|
@ -1034,6 +1035,12 @@ public class FileDataStorageManager {
|
|||
ocFile.setSharees(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
String metadataSize = cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_METADATA_SIZE));
|
||||
ImageDimension imageDimension = new Gson().fromJson(metadataSize, ImageDimension.class);
|
||||
|
||||
if (imageDimension != null) {
|
||||
ocFile.setImageDimension(imageDimension);
|
||||
}
|
||||
}
|
||||
|
||||
return ocFile;
|
||||
|
|
|
@ -22,4 +22,18 @@
|
|||
|
||||
package com.owncloud.android.datamodel
|
||||
|
||||
data class GalleryItems(val date: Long, val files: List<OCFile>)
|
||||
import com.owncloud.android.utils.DisplayUtils
|
||||
|
||||
data class GalleryItems(val date: Long, val rows: List<GalleryRow>) {
|
||||
override fun toString(): String {
|
||||
val month = DisplayUtils.getDateByPattern(
|
||||
date,
|
||||
DisplayUtils.MONTH_PATTERN
|
||||
)
|
||||
val year = DisplayUtils.getDateByPattern(
|
||||
date,
|
||||
DisplayUtils.YEAR_PATTERN
|
||||
)
|
||||
return "$month/$year with $rows rows"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2022 Tobias Kaminsky
|
||||
* Copyright (C) 2022 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel
|
||||
|
||||
data class GalleryRow(val files: List<OCFile>, val defaultHeight: Int, val defaultWidth: Int) {
|
||||
fun getMaxHeight(): Float {
|
||||
return files.map { it.imageDimension?.height ?: defaultHeight.toFloat() }.maxOrNull() ?: 0f
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2022 Tobias Kaminsky
|
||||
* Copyright (C) 2022 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.datamodel
|
||||
|
||||
data class ImageDimension(var width: Float = -1f, var height: Float = -1f)
|
|
@ -110,6 +110,8 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
|||
private long lockTimeout;
|
||||
@Nullable
|
||||
private String lockToken;
|
||||
@Nullable
|
||||
private ImageDimension imageDimension;
|
||||
|
||||
/**
|
||||
* URI to the local path of the file contents, if stored in the device; cached after first call to {@link
|
||||
|
@ -502,6 +504,8 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
|||
lockTimestamp = 0;
|
||||
lockTimeout = 0;
|
||||
lockToken = null;
|
||||
|
||||
imageDimension = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -948,4 +952,13 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
|||
public void setLockToken(@Nullable String lockToken) {
|
||||
this.lockToken = lockToken;
|
||||
}
|
||||
|
||||
public void setImageDimension(@Nullable ImageDimension imageDimension) {
|
||||
this.imageDimension = imageDimension;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ImageDimension getImageDimension() {
|
||||
return imageDimension;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ import com.owncloud.android.ui.adapter.DiskLruImageCache;
|
|||
import com.owncloud.android.ui.fragment.FileFragment;
|
||||
import com.owncloud.android.ui.preview.PreviewImageFragment;
|
||||
import com.owncloud.android.utils.BitmapUtils;
|
||||
import com.owncloud.android.utils.DisplayUtils;
|
||||
import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
import com.owncloud.android.utils.MimeTypeUtil;
|
||||
|
@ -79,6 +80,7 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
|
@ -251,6 +253,164 @@ public final class ThumbnailsCacheManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static class GalleryImageGenerationTaskObject {
|
||||
private final OCFile file;
|
||||
private final String imageKey;
|
||||
|
||||
public GalleryImageGenerationTaskObject(OCFile file, String imageKey) {
|
||||
this.file = file;
|
||||
this.imageKey = imageKey;
|
||||
}
|
||||
|
||||
private OCFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
private String getImageKey() {
|
||||
return imageKey;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GalleryImageGenerationTask extends AsyncTask<Object, Void, Bitmap> {
|
||||
private final User user;
|
||||
private final FileDataStorageManager storageManager;
|
||||
private final WeakReference<ImageView> imageViewReference;
|
||||
private OCFile file;
|
||||
private String imageKey;
|
||||
private GalleryListener listener;
|
||||
private List<GalleryImageGenerationTask> asyncTasks;
|
||||
private int backgroundColor;
|
||||
private boolean newImage = false;
|
||||
|
||||
public GalleryImageGenerationTask(
|
||||
ImageView imageView,
|
||||
User user,
|
||||
FileDataStorageManager storageManager,
|
||||
List<GalleryImageGenerationTask> asyncTasks,
|
||||
String imageKey,
|
||||
int backgroundColor
|
||||
) {
|
||||
this.user = user;
|
||||
this.storageManager = storageManager;
|
||||
imageViewReference = new WeakReference<>(imageView);
|
||||
this.asyncTasks = asyncTasks;
|
||||
this.imageKey = imageKey;
|
||||
this.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
public void setListener(GalleryImageGenerationTask.GalleryListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public String getImageKey() {
|
||||
return imageKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap doInBackground(Object... params) {
|
||||
Bitmap thumbnail;
|
||||
|
||||
file = (OCFile) params[0];
|
||||
|
||||
// try {
|
||||
// Thread.sleep(10000);
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
if (file.getRemoteId() != null && file.isPreviewAvailable()) {
|
||||
// Thumbnail in cache?
|
||||
thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
|
||||
ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.getRemoteId()
|
||||
);
|
||||
|
||||
if (thumbnail != null && !file.isUpdateThumbnailNeeded()) {
|
||||
Float size = (float) ThumbnailsCacheManager.getThumbnailDimension();
|
||||
|
||||
// resized dimensions
|
||||
ImageDimension imageDimension = file.getImageDimension();
|
||||
if (imageDimension == null ||
|
||||
imageDimension.getWidth() != size ||
|
||||
imageDimension.getHeight() != size) {
|
||||
file.setImageDimension(new ImageDimension(thumbnail.getWidth(), thumbnail.getHeight()));
|
||||
storageManager.saveFile(file);
|
||||
}
|
||||
|
||||
if (MimeTypeUtil.isVideo(file)) {
|
||||
return ThumbnailsCacheManager.addVideoOverlay(thumbnail, MainApp.getAppContext());
|
||||
} else {
|
||||
return thumbnail;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
mClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(),
|
||||
MainApp.getAppContext());
|
||||
|
||||
thumbnail = doResizedImageInBackground(file, storageManager);
|
||||
newImage = true;
|
||||
|
||||
if (MimeTypeUtil.isVideo(file) && thumbnail != null) {
|
||||
thumbnail = addVideoOverlay(thumbnail, MainApp.getAppContext());
|
||||
}
|
||||
|
||||
} catch (OutOfMemoryError oome) {
|
||||
Log_OC.e(TAG, "Out of memory");
|
||||
} catch (Throwable t) {
|
||||
// the app should never break due to a problem with thumbnails
|
||||
Log_OC.e(TAG, "Generation of gallery image for " + file + " failed", t);
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap bitmap) {
|
||||
if (bitmap != null && imageViewReference != null) {
|
||||
final ImageView imageView = imageViewReference.get();
|
||||
final GalleryImageGenerationTask bitmapWorkerTask = getGalleryImageGenerationTask(imageView);
|
||||
|
||||
if (this == bitmapWorkerTask) {
|
||||
String tagId = String.valueOf(file.getFileId());
|
||||
|
||||
if (String.valueOf(imageView.getTag()).equals(tagId)) {
|
||||
if ("image/png".equalsIgnoreCase(file.getMimeType())) {
|
||||
imageView.setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
if (newImage && listener != null) {
|
||||
listener.onNewGalleryImage();
|
||||
}
|
||||
imageView.setImageBitmap(bitmap);
|
||||
imageView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onSuccess();
|
||||
}
|
||||
} else {
|
||||
if (listener != null) {
|
||||
listener.onError();
|
||||
}
|
||||
}
|
||||
|
||||
if (asyncTasks != null) {
|
||||
asyncTasks.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public interface GalleryListener {
|
||||
void onSuccess();
|
||||
|
||||
void onNewGalleryImage();
|
||||
|
||||
void onError();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResizedImageGenerationTask extends AsyncTask<Object, Void, Bitmap> {
|
||||
private final FileFragment fileFragment;
|
||||
private final FileDataStorageManager storageManager;
|
||||
|
@ -288,10 +448,10 @@ public final class ThumbnailsCacheManager {
|
|||
mClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(),
|
||||
MainApp.getAppContext());
|
||||
|
||||
thumbnail = doResizedImageInBackground();
|
||||
thumbnail = doResizedImageInBackground(file, storageManager);
|
||||
|
||||
if (MimeTypeUtil.isVideo(file) && thumbnail != null) {
|
||||
thumbnail = addVideoOverlay(thumbnail);
|
||||
thumbnail = addVideoOverlay(thumbnail, MainApp.getAppContext());
|
||||
}
|
||||
|
||||
} catch (OutOfMemoryError oome) {
|
||||
|
@ -304,79 +464,6 @@ public final class ThumbnailsCacheManager {
|
|||
return thumbnail;
|
||||
}
|
||||
|
||||
private Bitmap doResizedImageInBackground() {
|
||||
Bitmap thumbnail;
|
||||
|
||||
String imageKey = PREFIX_RESIZED_IMAGE + file.getRemoteId();
|
||||
|
||||
// Check disk cache in background thread
|
||||
thumbnail = getBitmapFromDiskCache(imageKey);
|
||||
|
||||
// Not found in disk cache
|
||||
if (thumbnail == null || file.isUpdateThumbnailNeeded()) {
|
||||
Point p = getScreenDimension();
|
||||
int pxW = p.x;
|
||||
int pxH = p.y;
|
||||
|
||||
if (file.isDown()) {
|
||||
Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getStoragePath(), pxW, pxH);
|
||||
|
||||
if (bitmap != null) {
|
||||
// Handle PNG
|
||||
if (PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) {
|
||||
bitmap = handlePNG(bitmap, pxW, pxH);
|
||||
}
|
||||
|
||||
thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), pxW, pxH);
|
||||
|
||||
file.setUpdateThumbnailNeeded(false);
|
||||
storageManager.saveFile(file);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Download thumbnail from server
|
||||
if (mClient != null) {
|
||||
GetMethod getMethod = null;
|
||||
try {
|
||||
String uri = mClient.getBaseUri() + "/index.php/core/preview.png?file="
|
||||
+ URLEncoder.encode(file.getRemotePath())
|
||||
+ "&x=" + pxW + "&y=" + pxH + "&a=1&mode=cover&forceIcon=0";
|
||||
getMethod = new GetMethod(uri);
|
||||
|
||||
int status = mClient.executeMethod(getMethod);
|
||||
if (status == HttpStatus.SC_OK) {
|
||||
InputStream inputStream = getMethod.getResponseBodyAsStream();
|
||||
thumbnail = BitmapFactory.decodeStream(inputStream);
|
||||
} else {
|
||||
mClient.exhaustResponse(getMethod.getResponseBodyAsStream());
|
||||
}
|
||||
|
||||
// Handle PNG
|
||||
if (thumbnail != null && PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) {
|
||||
thumbnail = handlePNG(thumbnail, thumbnail.getWidth(), thumbnail.getHeight());
|
||||
}
|
||||
|
||||
// Add thumbnail to cache
|
||||
if (thumbnail != null) {
|
||||
Log_OC.d(TAG, "add thumbnail to cache: " + file.getFileName());
|
||||
addBitmapToCache(imageKey, thumbnail);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log_OC.d(TAG, e.getMessage(), e);
|
||||
} finally {
|
||||
if (getMethod != null) {
|
||||
getMethod.releaseConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap bitmap) {
|
||||
if (imageViewReference != null) {
|
||||
final ImageView imageView = imageViewReference.get();
|
||||
|
@ -516,7 +603,7 @@ public final class ThumbnailsCacheManager {
|
|||
thumbnail = doThumbnailFromOCFileInBackground();
|
||||
|
||||
if (MimeTypeUtil.isVideo((ServerFileInterface) mFile) && thumbnail != null) {
|
||||
thumbnail = addVideoOverlay(thumbnail);
|
||||
thumbnail = addVideoOverlay(thumbnail, MainApp.getAppContext());
|
||||
}
|
||||
} else if (mFile instanceof File) {
|
||||
thumbnail = doFileInBackground();
|
||||
|
@ -525,7 +612,7 @@ public final class ThumbnailsCacheManager {
|
|||
String mMimeType = FileStorageUtils.getMimeTypeFromName(url);
|
||||
|
||||
if (MimeTypeUtil.isVideo(mMimeType) && thumbnail != null) {
|
||||
thumbnail = addVideoOverlay(thumbnail);
|
||||
thumbnail = addVideoOverlay(thumbnail, MainApp.getAppContext());
|
||||
}
|
||||
//} else { do nothing
|
||||
}
|
||||
|
@ -1101,22 +1188,31 @@ public final class ThumbnailsCacheManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static Bitmap addVideoOverlay(Bitmap thumbnail) {
|
||||
int playButtonWidth = (int) (thumbnail.getWidth() * 0.3);
|
||||
int playButtonHeight = (int) (thumbnail.getHeight() * 0.3);
|
||||
private static GalleryImageGenerationTask getGalleryImageGenerationTask(ImageView imageView) {
|
||||
if (imageView != null) {
|
||||
final Drawable drawable = imageView.getDrawable();
|
||||
if (drawable instanceof AsyncGalleryImageDrawable) {
|
||||
final AsyncGalleryImageDrawable asyncDrawable = (AsyncGalleryImageDrawable) drawable;
|
||||
return asyncDrawable.getBitmapWorkerTask();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Bitmap addVideoOverlay(Bitmap thumbnail, Context context) {
|
||||
// int minValue = Math.min(thumbnail.getWidth(), thumbnail.getHeight());
|
||||
// int playButtonWidth = (int) (minValue * 0.2);
|
||||
// int playButtonHeight = (int) (minValue * 0.2);
|
||||
|
||||
Drawable playButtonDrawable = ResourcesCompat.getDrawable(MainApp.getAppContext().getResources(),
|
||||
R.drawable.view_play,
|
||||
R.drawable.video_white,
|
||||
null);
|
||||
|
||||
Bitmap playButton = BitmapUtils.drawableToBitmap(playButtonDrawable,
|
||||
playButtonWidth,
|
||||
playButtonHeight);
|
||||
int px = DisplayUtils.convertDpToPixel(24f, context);
|
||||
|
||||
Bitmap resizedPlayButton = Bitmap.createScaledBitmap(playButton,
|
||||
playButtonWidth,
|
||||
playButtonHeight,
|
||||
true);
|
||||
Bitmap playButton = BitmapUtils.drawableToBitmap(playButtonDrawable, px, px);
|
||||
|
||||
Bitmap resizedPlayButton = Bitmap.createScaledBitmap(playButton, px, px, true);
|
||||
|
||||
Bitmap resultBitmap = Bitmap.createBitmap(thumbnail.getWidth(),
|
||||
thumbnail.getHeight(),
|
||||
|
@ -1124,32 +1220,31 @@ public final class ThumbnailsCacheManager {
|
|||
|
||||
Canvas c = new Canvas(resultBitmap);
|
||||
|
||||
// compute visual center of play button, according to resized image
|
||||
int x1 = resizedPlayButton.getWidth();
|
||||
int y1 = resizedPlayButton.getHeight() / 2;
|
||||
int x2 = 0;
|
||||
int y2 = resizedPlayButton.getWidth();
|
||||
int x3 = 0;
|
||||
int y3 = 0;
|
||||
|
||||
double ym = ( ((Math.pow(x3,2) - Math.pow(x1,2) + Math.pow(y3,2) - Math.pow(y1,2)) *
|
||||
(x2 - x1)) - (Math.pow(x2,2) - Math.pow(x1,2) + Math.pow(y2,2) -
|
||||
Math.pow(y1,2)) * (x3 - x1) ) / (2 * ( ((y3 - y1) * (x2 - x1)) -
|
||||
((y2 - y1) * (x3 - x1)) ));
|
||||
double xm = ( (Math.pow(x2,2) - Math.pow(x1,2)) + (Math.pow(y2,2) - Math.pow(y1,2)) -
|
||||
(2*ym*(y2 - y1)) ) / (2*(x2 - x1));
|
||||
|
||||
// offset to top left
|
||||
double ox = - xm;
|
||||
|
||||
// // compute visual center of play button, according to resized image
|
||||
// int x1 = resizedPlayButton.getWidth();
|
||||
// int y1 = resizedPlayButton.getHeight() / 2;
|
||||
// int x2 = 0;
|
||||
// int y2 = resizedPlayButton.getWidth();
|
||||
// int x3 = 0;
|
||||
// int y3 = 0;
|
||||
//
|
||||
// double ym = ( ((Math.pow(x3,2) - Math.pow(x1,2) + Math.pow(y3,2) - Math.pow(y1,2)) *
|
||||
// (x2 - x1)) - (Math.pow(x2,2) - Math.pow(x1,2) + Math.pow(y2,2) -
|
||||
// Math.pow(y1,2)) * (x3 - x1) ) / (2 * ( ((y3 - y1) * (x2 - x1)) -
|
||||
// ((y2 - y1) * (x3 - x1)) ));
|
||||
// double xm = ( (Math.pow(x2,2) - Math.pow(x1,2)) + (Math.pow(y2,2) - Math.pow(y1,2)) -
|
||||
// (2*ym*(y2 - y1)) ) / (2*(x2 - x1));
|
||||
//
|
||||
// // offset to top left
|
||||
// double ox = - xm;
|
||||
//
|
||||
|
||||
c.drawBitmap(thumbnail, 0, 0, null);
|
||||
|
||||
Paint p = new Paint();
|
||||
p.setAlpha(230);
|
||||
|
||||
c.drawBitmap(resizedPlayButton, (float) ((thumbnail.getWidth() / 2) + ox),
|
||||
(float) ((thumbnail.getHeight() / 2) - ym), p);
|
||||
c.drawBitmap(resizedPlayButton, px, px, p);
|
||||
|
||||
return resultBitmap;
|
||||
}
|
||||
|
@ -1183,6 +1278,19 @@ public final class ThumbnailsCacheManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static class AsyncGalleryImageDrawable extends BitmapDrawable {
|
||||
private final WeakReference<GalleryImageGenerationTask> bitmapWorkerTaskReference;
|
||||
|
||||
public AsyncGalleryImageDrawable(Resources res, Bitmap bitmap, GalleryImageGenerationTask bitmapWorkerTask) {
|
||||
super(res, bitmap);
|
||||
bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
|
||||
}
|
||||
|
||||
private GalleryImageGenerationTask getBitmapWorkerTask() {
|
||||
return bitmapWorkerTaskReference.get();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AsyncMediaThumbnailDrawable extends BitmapDrawable {
|
||||
|
||||
public AsyncMediaThumbnailDrawable(Resources res, Bitmap bitmap) {
|
||||
|
@ -1291,4 +1399,87 @@ public final class ThumbnailsCacheManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void clearCache() {
|
||||
mThumbnailCache.clearCache();
|
||||
}
|
||||
|
||||
private static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageManager storageManager) {
|
||||
Bitmap thumbnail;
|
||||
|
||||
String imageKey = PREFIX_RESIZED_IMAGE + file.getRemoteId();
|
||||
|
||||
// Check disk cache in background thread
|
||||
thumbnail = getBitmapFromDiskCache(imageKey);
|
||||
|
||||
// Not found in disk cache
|
||||
if (thumbnail == null || file.isUpdateThumbnailNeeded()) {
|
||||
Point p = getScreenDimension();
|
||||
int pxW = p.x;
|
||||
int pxH = p.y;
|
||||
|
||||
if (file.isDown()) {
|
||||
Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getStoragePath(), pxW, pxH);
|
||||
|
||||
if (bitmap != null) {
|
||||
// Handle PNG
|
||||
if (PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) {
|
||||
bitmap = handlePNG(bitmap, pxW, pxH);
|
||||
}
|
||||
|
||||
thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), pxW, pxH);
|
||||
|
||||
file.setUpdateThumbnailNeeded(false);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Download thumbnail from server
|
||||
if (mClient != null) {
|
||||
GetMethod getMethod = null;
|
||||
try {
|
||||
String uri = mClient.getBaseUri() + "/index.php/core/preview.png?file="
|
||||
+ URLEncoder.encode(file.getRemotePath())
|
||||
+ "&x=" + (pxW / 2) + "&y=" + (pxH / 2) + "&a=1&mode=cover&forceIcon=0";
|
||||
Log_OC.d(TAG, "generate resized image: " + file.getFileName() + " URI: " + uri);
|
||||
getMethod = new GetMethod(uri);
|
||||
|
||||
int status = mClient.executeMethod(getMethod);
|
||||
if (status == HttpStatus.SC_OK) {
|
||||
InputStream inputStream = getMethod.getResponseBodyAsStream();
|
||||
thumbnail = BitmapFactory.decodeStream(inputStream);
|
||||
} else {
|
||||
mClient.exhaustResponse(getMethod.getResponseBodyAsStream());
|
||||
}
|
||||
|
||||
// Handle PNG
|
||||
if (thumbnail != null && PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) {
|
||||
thumbnail = handlePNG(thumbnail, thumbnail.getWidth(), thumbnail.getHeight());
|
||||
}
|
||||
|
||||
// Add thumbnail to cache
|
||||
if (thumbnail != null) {
|
||||
Log_OC.d(TAG, "add resized image to cache: " + file.getFileName());
|
||||
addBitmapToCache(imageKey, thumbnail);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log_OC.d(TAG, e.getMessage(), e);
|
||||
} finally {
|
||||
if (getMethod != null) {
|
||||
getMethod.releaseConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resized dimensions
|
||||
if (thumbnail != null) {
|
||||
file.setImageDimension(new ImageDimension(thumbnail.getWidth(), thumbnail.getHeight()));
|
||||
storageManager.saveFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ import java.util.List;
|
|||
*/
|
||||
public class ProviderMeta {
|
||||
public static final String DB_NAME = "filelist";
|
||||
public static final int DB_VERSION = 63;
|
||||
public static final int DB_VERSION = 64;
|
||||
|
||||
private ProviderMeta() {
|
||||
// No instance
|
||||
|
@ -117,6 +117,7 @@ public class ProviderMeta {
|
|||
public static final String FILE_NOTE = "note";
|
||||
public static final String FILE_SHAREES = "sharees";
|
||||
public static final String FILE_RICH_WORKSPACE = "rich_workspace";
|
||||
public static final String FILE_METADATA_SIZE = "metadata_size";
|
||||
public static final String FILE_LOCKED = "locked";
|
||||
public static final String FILE_LOCK_TYPE = "lock_type";
|
||||
public static final String FILE_LOCK_OWNER = "lock_owner";
|
||||
|
@ -169,7 +170,8 @@ public class ProviderMeta {
|
|||
FILE_LOCK_OWNER_EDITOR,
|
||||
FILE_LOCK_TIMESTAMP,
|
||||
FILE_LOCK_TIMEOUT,
|
||||
FILE_LOCK_TOKEN));
|
||||
FILE_LOCK_TOKEN,
|
||||
FILE_METADATA_SIZE));
|
||||
public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc";
|
||||
|
||||
// Columns of ocshares table
|
||||
|
|
|
@ -506,6 +506,11 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
// add to updatedFile data about LOCAL STATE (not existing in server)
|
||||
updatedFile.setLastSyncDateForProperties(mCurrentSyncTime);
|
||||
|
||||
// keep thumbnail info
|
||||
if (!updatedFile.isUpdateThumbnailNeeded() && localFile != null && localFile.getImageDimension() != null) {
|
||||
updatedFile.setImageDimension(localFile.getImageDimension());
|
||||
}
|
||||
|
||||
// add to updatedFile data from local and remote file
|
||||
setLocalFileDataOnUpdatedFile(remoteFile, localFile, updatedFile, mRemoteFolderChanged);
|
||||
|
||||
|
|
|
@ -754,6 +754,7 @@ public class FileContentProvider extends ContentProvider {
|
|||
+ ProviderTableMeta.FILE_NOTE + TEXT
|
||||
+ ProviderTableMeta.FILE_SHAREES + TEXT
|
||||
+ ProviderTableMeta.FILE_RICH_WORKSPACE + TEXT
|
||||
+ ProviderTableMeta.FILE_METADATA_SIZE + TEXT
|
||||
+ ProviderTableMeta.FILE_LOCKED + INTEGER // boolean
|
||||
+ ProviderTableMeta.FILE_LOCK_TYPE + INTEGER
|
||||
+ ProviderTableMeta.FILE_LOCK_OWNER + TEXT
|
||||
|
@ -2499,6 +2500,24 @@ public class FileContentProvider extends ContentProvider {
|
|||
if (!upgraded) {
|
||||
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
|
||||
}
|
||||
|
||||
if (oldVersion < 64 && newVersion >= 64) {
|
||||
Log_OC.i(SQL, "Entering in the #64 add metadata size to files");
|
||||
db.beginTransaction();
|
||||
try {
|
||||
db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
|
||||
ADD_COLUMN + ProviderTableMeta.FILE_METADATA_SIZE + " TEXT ");
|
||||
|
||||
upgraded = true;
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
if (!upgraded) {
|
||||
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ public abstract class EditorWebView extends ExternalSiteWebView {
|
|||
|
||||
if (thumbnail != null && !file.isUpdateThumbnailNeeded()) {
|
||||
if (MimeTypeUtil.isVideo(file)) {
|
||||
Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail);
|
||||
Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail, this);
|
||||
binding.thumbnail.setImageBitmap(withOverlay);
|
||||
} else {
|
||||
binding.thumbnail.setImageBitmap(thumbnail);
|
||||
|
|
|
@ -36,9 +36,10 @@ import com.afollestad.sectionedrecyclerview.SectionedViewHolder
|
|||
import com.nextcloud.client.account.User
|
||||
import com.nextcloud.client.preferences.AppPreferences
|
||||
import com.owncloud.android.databinding.GalleryHeaderBinding
|
||||
import com.owncloud.android.databinding.GridImageBinding
|
||||
import com.owncloud.android.databinding.GalleryRowBinding
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.datamodel.GalleryItems
|
||||
import com.owncloud.android.datamodel.GalleryRow
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import com.owncloud.android.ui.activity.ComponentsGetter
|
||||
import com.owncloud.android.ui.fragment.GalleryFragment
|
||||
|
@ -47,7 +48,6 @@ import com.owncloud.android.ui.fragment.SearchType
|
|||
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface
|
||||
import com.owncloud.android.utils.DisplayUtils
|
||||
import com.owncloud.android.utils.FileSortOrder
|
||||
import com.owncloud.android.utils.FileStorageUtils
|
||||
import com.owncloud.android.utils.MimeTypeUtil
|
||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
@ -61,7 +61,9 @@ class GalleryAdapter(
|
|||
ocFileListFragmentInterface: OCFileListFragmentInterface,
|
||||
preferences: AppPreferences,
|
||||
transferServiceGetter: ComponentsGetter,
|
||||
viewThemeUtils: ViewThemeUtils
|
||||
viewThemeUtils: ViewThemeUtils,
|
||||
var columns: Int,
|
||||
val defaultThumbnailSize: Int
|
||||
) : SectionedRecyclerViewAdapter<SectionedViewHolder>(), CommonOCFileListAdapterInterface, PopupTextProvider {
|
||||
var files: List<GalleryItems> = mutableListOf()
|
||||
private val ocFileListDelegate: OCFileListDelegate
|
||||
|
@ -97,8 +99,12 @@ class GalleryAdapter(
|
|||
)
|
||||
)
|
||||
} else {
|
||||
GalleryItemViewHolder(
|
||||
GridImageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
GalleryRowHolder(
|
||||
GalleryRowBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
defaultThumbnailSize.toFloat(),
|
||||
ocFileListDelegate,
|
||||
storageManager,
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -110,19 +116,13 @@ class GalleryAdapter(
|
|||
absolutePosition: Int
|
||||
) {
|
||||
if (holder != null) {
|
||||
val itemViewHolder = holder as GalleryItemViewHolder
|
||||
val ocFile = files[section].files[relativePosition]
|
||||
|
||||
ocFileListDelegate.bindGridViewHolder(
|
||||
itemViewHolder,
|
||||
ocFile,
|
||||
SearchType.GALLERY_SEARCH
|
||||
)
|
||||
val rowHolder = holder as GalleryRowHolder
|
||||
rowHolder.bind(files[section].rows[relativePosition])
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(section: Int): Int {
|
||||
return files[section].files.size
|
||||
return files[section].rows.size
|
||||
}
|
||||
|
||||
override fun getSectionCount(): Int {
|
||||
|
@ -199,12 +199,20 @@ class GalleryAdapter(
|
|||
|
||||
files = finalSortedList
|
||||
.groupBy { firstOfMonth(it.modificationTimestamp) }
|
||||
.map { GalleryItems(it.key, FileStorageUtils.sortOcFolderDescDateModifiedWithoutFavoritesFirst(it.value)) }
|
||||
.map { GalleryItems(it.key, transformToRows(it.value)) }
|
||||
.sortedBy { it.date }.reversed()
|
||||
|
||||
Handler(Looper.getMainLooper()).post { notifyDataSetChanged() }
|
||||
}
|
||||
|
||||
private fun transformToRows(list: List<OCFile>): List<GalleryRow> {
|
||||
return list
|
||||
.sortedBy { it.modificationTimestamp }
|
||||
.reversed()
|
||||
.chunked(columns)
|
||||
.map { entry -> GalleryRow(entry, defaultThumbnailSize, defaultThumbnailSize) }
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun clear() {
|
||||
files = emptyList()
|
||||
|
@ -227,10 +235,14 @@ class GalleryAdapter(
|
|||
}
|
||||
|
||||
fun getItem(position: Int): OCFile? {
|
||||
val itemCoord = getRelativePosition(position)
|
||||
val itemCoordinates = getRelativePosition(position)
|
||||
|
||||
return files
|
||||
.getOrNull(itemCoord.section())?.files
|
||||
?.getOrNull(itemCoord.relativePos())
|
||||
.getOrNull(itemCoordinates.section())
|
||||
?.rows
|
||||
?.getOrNull(itemCoordinates.relativePos())
|
||||
?.files
|
||||
?.getOrNull(0)
|
||||
}
|
||||
|
||||
override fun isMultiSelect(): Boolean {
|
||||
|
@ -242,8 +254,27 @@ class GalleryAdapter(
|
|||
}
|
||||
|
||||
override fun getItemPosition(file: OCFile): Int {
|
||||
val item = files.find { it.files.contains(file) }
|
||||
return getAbsolutePosition(files.indexOf(item), item?.files?.indexOf(file) ?: 0)
|
||||
var item: Int? = null
|
||||
var row: Int? = null
|
||||
for (galleryItem in files.withIndex()) {
|
||||
if (item != null) {
|
||||
break
|
||||
}
|
||||
for (galleryRow in galleryItem.value.rows.withIndex()) {
|
||||
if (galleryRow.value.files.contains(file)) {
|
||||
item = galleryItem.index
|
||||
row = galleryRow.index
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// month, row
|
||||
return if (item == null || row == null) {
|
||||
getAbsolutePosition(0, 0)
|
||||
} else {
|
||||
getAbsolutePosition(item, row)
|
||||
}
|
||||
}
|
||||
|
||||
override fun swapDirectory(
|
||||
|
@ -285,7 +316,7 @@ class GalleryAdapter(
|
|||
}
|
||||
|
||||
override fun getFilesCount(): Int {
|
||||
return files.fold(0) { acc, item -> acc + item.files.size }
|
||||
return files.fold(0) { acc, item -> acc + item.rows.size }
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
|
@ -302,4 +333,8 @@ class GalleryAdapter(
|
|||
fun addFiles(items: List<GalleryItems>) {
|
||||
files = items
|
||||
}
|
||||
|
||||
fun changeColumn(newColumn: Int) {
|
||||
columns = newColumn
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,35 +22,12 @@
|
|||
|
||||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
import com.owncloud.android.databinding.GridImageBinding
|
||||
|
||||
class GalleryItemViewHolder(val binding: GridImageBinding) :
|
||||
SectionedViewHolder(binding.root), ListGridImageViewHolder {
|
||||
override val thumbnail: ImageView
|
||||
SectionedViewHolder(binding.root) {
|
||||
val thumbnail: ImageView
|
||||
get() = binding.thumbnail
|
||||
|
||||
override val shimmerThumbnail: LoaderImageView
|
||||
get() = binding.thumbnailShimmer
|
||||
|
||||
override val favorite: ImageView
|
||||
get() = binding.favoriteAction
|
||||
|
||||
override val localFileIndicator: ImageView
|
||||
get() = binding.localFileIndicator
|
||||
|
||||
override val shared: ImageView
|
||||
get() = binding.sharedIcon
|
||||
|
||||
override val checkbox: ImageView
|
||||
get() = binding.customCheckbox
|
||||
|
||||
override val itemLayout: View
|
||||
get() = binding.ListItemLayout
|
||||
|
||||
override val unreadComments: ImageView
|
||||
get() = binding.unreadComments
|
||||
}
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2022 Tobias Kaminsky
|
||||
* Copyright (C) 2022 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.get
|
||||
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.databinding.GalleryRowBinding
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.datamodel.GalleryRow
|
||||
import com.owncloud.android.datamodel.ImageDimension
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager
|
||||
import com.owncloud.android.utils.BitmapUtils
|
||||
import com.owncloud.android.utils.DisplayUtils
|
||||
|
||||
class GalleryRowHolder(
|
||||
val binding: GalleryRowBinding,
|
||||
private val defaultThumbnailSize: Float,
|
||||
private val ocFileListDelegate: OCFileListDelegate,
|
||||
val storageManager: FileDataStorageManager,
|
||||
private val galleryAdapter: GalleryAdapter
|
||||
) : SectionedViewHolder(binding.root) {
|
||||
val context = galleryAdapter.context
|
||||
|
||||
lateinit var currentRow: GalleryRow
|
||||
|
||||
fun bind(row: GalleryRow) {
|
||||
currentRow = row
|
||||
|
||||
// re-use existing ones
|
||||
while (binding.rowLayout.childCount < row.files.size) {
|
||||
val shimmer = LoaderImageView(context).apply {
|
||||
setImageResource(R.drawable.background)
|
||||
resetLoader()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
val imageView = ImageView(context).apply {
|
||||
setImageDrawable(
|
||||
ThumbnailsCacheManager.AsyncGalleryImageDrawable(
|
||||
context.resources,
|
||||
BitmapUtils.drawableToBitmap(
|
||||
ResourcesCompat.getDrawable(resources, R.drawable.file_image, null),
|
||||
defaultThumbnailSize.toInt(),
|
||||
defaultThumbnailSize.toInt()
|
||||
),
|
||||
null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
LinearLayout(context).apply {
|
||||
addView(shimmer)
|
||||
addView(imageView)
|
||||
|
||||
binding.rowLayout.addView(this)
|
||||
}
|
||||
}
|
||||
|
||||
if (binding.rowLayout.childCount > row.files.size) {
|
||||
binding.rowLayout.removeViewsInLayout(row.files.size - 1, (binding.rowLayout.childCount - row.files.size))
|
||||
}
|
||||
|
||||
val shrinkRatio = computeShrinkRatio(row)
|
||||
|
||||
for (indexedFile in row.files.withIndex()) {
|
||||
adjustFile(indexedFile, shrinkRatio, row)
|
||||
}
|
||||
}
|
||||
|
||||
fun redraw() {
|
||||
bind(currentRow)
|
||||
}
|
||||
|
||||
@SuppressWarnings("MagicNumber", "ComplexMethod")
|
||||
private fun computeShrinkRatio(row: GalleryRow): Float {
|
||||
val screenWidth =
|
||||
DisplayUtils.convertDpToPixel(context.resources.configuration.screenWidthDp.toFloat(), context)
|
||||
.toFloat()
|
||||
|
||||
if (row.files.size > 1) {
|
||||
var newSummedWidth = 0f
|
||||
for (file in row.files) {
|
||||
// first adjust all thumbnails to max height
|
||||
val thumbnail1 = file.imageDimension ?: ImageDimension(defaultThumbnailSize, defaultThumbnailSize)
|
||||
|
||||
val height1 = thumbnail1.height
|
||||
val width1 = thumbnail1.width
|
||||
|
||||
val scaleFactor1 = row.getMaxHeight() / height1
|
||||
val newHeight1 = height1 * scaleFactor1
|
||||
val newWidth1 = width1 * scaleFactor1
|
||||
|
||||
file.imageDimension = ImageDimension(newWidth1, newHeight1)
|
||||
|
||||
newSummedWidth += newWidth1
|
||||
}
|
||||
|
||||
var c = 1f
|
||||
// this ensures that files in last row are better visible,
|
||||
// e.g. when 2 images are there, it uses 2/5 of screen
|
||||
if (galleryAdapter.columns == 5) {
|
||||
when (row.files.size) {
|
||||
2 -> {
|
||||
c = 5 / 2f
|
||||
}
|
||||
3 -> {
|
||||
c = 4 / 3f
|
||||
}
|
||||
4 -> {
|
||||
c = 4 / 5f
|
||||
}
|
||||
5 -> {
|
||||
c = 1f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (screenWidth / c) / newSummedWidth
|
||||
} else {
|
||||
val thumbnail1 = row.files[0].imageDimension ?: ImageDimension(defaultThumbnailSize, defaultThumbnailSize)
|
||||
return (screenWidth / galleryAdapter.columns) / thumbnail1.width
|
||||
}
|
||||
}
|
||||
|
||||
private fun adjustFile(indexedFile: IndexedValue<OCFile>, shrinkRatio: Float, row: GalleryRow) {
|
||||
val file = indexedFile.value
|
||||
val index = indexedFile.index
|
||||
|
||||
val adjustedHeight1 = ((file.imageDimension?.height ?: defaultThumbnailSize) * shrinkRatio).toInt()
|
||||
val adjustedWidth1 = ((file.imageDimension?.width ?: defaultThumbnailSize) * shrinkRatio).toInt()
|
||||
|
||||
// re-use existing one
|
||||
val linearLayout = binding.rowLayout[index] as LinearLayout
|
||||
val shimmer = linearLayout[0] as LoaderImageView
|
||||
|
||||
val thumbnail = linearLayout[1] as ImageView
|
||||
|
||||
thumbnail.adjustViewBounds = true
|
||||
thumbnail.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
|
||||
ocFileListDelegate.bindGalleryRowThumbnail(
|
||||
shimmer,
|
||||
thumbnail,
|
||||
file,
|
||||
this,
|
||||
adjustedWidth1
|
||||
)
|
||||
|
||||
val params = LinearLayout.LayoutParams(adjustedWidth1, adjustedHeight1)
|
||||
|
||||
val zero = context.resources.getInteger(R.integer.zero)
|
||||
val margin = context.resources.getInteger(R.integer.small_margin)
|
||||
if (index < (row.files.size - 1)) {
|
||||
params.setMargins(zero, zero, margin, margin)
|
||||
} else {
|
||||
params.setMargins(zero, zero, zero, margin)
|
||||
}
|
||||
|
||||
thumbnail.layoutParams = params
|
||||
thumbnail.layoutParams.height = adjustedHeight1
|
||||
thumbnail.layoutParams.width = adjustedWidth1
|
||||
|
||||
shimmer.layoutParams = params
|
||||
shimmer.layoutParams.height = adjustedHeight1
|
||||
shimmer.layoutParams.width = adjustedWidth1
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ import com.elyeproj.loaderviewlibrary.LoaderImageView
|
|||
|
||||
interface ListGridImageViewHolder {
|
||||
val thumbnail: ImageView
|
||||
fun showVideoOverlay()
|
||||
val shimmerThumbnail: LoaderImageView
|
||||
val favorite: ImageView
|
||||
val localFileIndicator: ImageView
|
||||
|
|
|
@ -23,11 +23,14 @@ package com.owncloud.android.ui.adapter
|
|||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
import com.nextcloud.client.account.User
|
||||
import com.nextcloud.client.preferences.AppPreferences
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTask
|
||||
import com.owncloud.android.lib.common.utils.Log_OC
|
||||
import com.owncloud.android.ui.activity.ComponentsGetter
|
||||
|
@ -54,6 +57,7 @@ class OCFileListDelegate(
|
|||
private var highlightedItem: OCFile? = null
|
||||
var isMultiSelect = false
|
||||
private val asyncTasks: MutableList<ThumbnailGenerationTask> = ArrayList()
|
||||
private val asyncGalleryTasks: MutableList<ThumbnailsCacheManager.GalleryImageGenerationTask> = ArrayList()
|
||||
fun setHighlightedItem(highlightedItem: OCFile?) {
|
||||
this.highlightedItem = highlightedItem
|
||||
}
|
||||
|
@ -87,6 +91,34 @@ class OCFileListDelegate(
|
|||
checkedFiles.clear()
|
||||
}
|
||||
|
||||
fun bindGalleryRowThumbnail(
|
||||
shimmer: LoaderImageView?,
|
||||
imageView: ImageView,
|
||||
file: OCFile,
|
||||
galleryRowHolder: GalleryRowHolder,
|
||||
width: Int
|
||||
) {
|
||||
// thumbnail
|
||||
imageView.tag = file.fileId
|
||||
DisplayUtils.setGalleryImage(
|
||||
file,
|
||||
imageView,
|
||||
user,
|
||||
storageManager,
|
||||
asyncGalleryTasks,
|
||||
gridView,
|
||||
context,
|
||||
shimmer,
|
||||
preferences,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils,
|
||||
galleryRowHolder,
|
||||
width
|
||||
)
|
||||
|
||||
imageView.setOnClickListener { ocFileListFragmentInterface.onItemClicked(file) }
|
||||
}
|
||||
|
||||
fun bindGridViewHolder(
|
||||
gridViewHolder: ListGridImageViewHolder,
|
||||
file: OCFile,
|
||||
|
|
|
@ -21,35 +21,14 @@
|
|||
*/
|
||||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
import com.owncloud.android.databinding.GridImageBinding
|
||||
|
||||
internal class OCFileListGridImageViewHolder(var binding: GridImageBinding) :
|
||||
RecyclerView.ViewHolder(
|
||||
binding.root
|
||||
),
|
||||
ListGridImageViewHolder {
|
||||
override val thumbnail: ImageView
|
||||
) {
|
||||
val thumbnail: ImageView
|
||||
get() = binding.thumbnail
|
||||
override val shimmerThumbnail: LoaderImageView
|
||||
get() = binding.thumbnailShimmer
|
||||
override val favorite: ImageView
|
||||
get() = binding.favoriteAction
|
||||
override val localFileIndicator: ImageView
|
||||
get() = binding.localFileIndicator
|
||||
override val shared: ImageView
|
||||
get() = binding.sharedIcon
|
||||
override val checkbox: ImageView
|
||||
get() = binding.customCheckbox
|
||||
override val itemLayout: View
|
||||
get() = binding.ListItemLayout
|
||||
override val unreadComments: ImageView
|
||||
get() = binding.unreadComments
|
||||
|
||||
init {
|
||||
binding.favoriteAction.drawable.mutate()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,11 @@ internal class OCFileListGridItemViewHolder(var binding: GridItemBinding) :
|
|||
get() = binding.Filename
|
||||
override val thumbnail: ImageView
|
||||
get() = binding.thumbnail
|
||||
|
||||
override fun showVideoOverlay() {
|
||||
binding.videoOverlay.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override val shimmerThumbnail: LoaderImageView
|
||||
get() = binding.thumbnailShimmer
|
||||
override val favorite: ImageView
|
||||
|
|
|
@ -48,6 +48,11 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) :
|
|||
get() = binding.Filename
|
||||
override val thumbnail: ImageView
|
||||
get() = binding.thumbnail
|
||||
|
||||
override fun showVideoOverlay() {
|
||||
binding.videoOverlay.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override val shimmerThumbnail: LoaderImageView
|
||||
get() = binding.thumbnailShimmer
|
||||
override val favorite: ImageView
|
||||
|
|
|
@ -232,7 +232,7 @@ public class TrashbinListAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
|
||||
if (thumbnail != null) {
|
||||
if (MimeTypeUtil.isVideo(file)) {
|
||||
Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail);
|
||||
Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail, context);
|
||||
thumbnailView.setImageBitmap(withOverlay);
|
||||
} else {
|
||||
thumbnailView.setImageBitmap(thumbnail);
|
||||
|
|
|
@ -373,7 +373,7 @@ public class ExtendedListFragment extends Fragment implements
|
|||
}
|
||||
}
|
||||
|
||||
private void setGridViewColumns(float scaleFactor) {
|
||||
protected void setGridViewColumns(float scaleFactor) {
|
||||
if (mRecyclerView.getLayoutManager() instanceof GridLayoutManager) {
|
||||
GridLayoutManager gridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
|
||||
if (mScale == -1f) {
|
||||
|
|
|
@ -25,6 +25,7 @@ package com.owncloud.android.ui.fragment;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -38,6 +39,7 @@ import com.nextcloud.utils.view.FastScrollUtils;
|
|||
import com.owncloud.android.R;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
||||
import com.owncloud.android.ui.activity.FolderPickerActivity;
|
||||
|
@ -75,6 +77,9 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
|
|||
|
||||
@Inject FileDataStorageManager fileDataStorageManager;
|
||||
@Inject FastScrollUtils fastScrollUtils;
|
||||
private final int maxColumnSizeLandscape = 5;
|
||||
private final int maxColumnSizePortrait = 2;
|
||||
private int columnSize;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -86,6 +91,12 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
|
|||
if (galleryFragmentBottomSheetDialog == null) {
|
||||
galleryFragmentBottomSheetDialog = new GalleryFragmentBottomSheetDialog(this);
|
||||
}
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
columnSize = maxColumnSizeLandscape;
|
||||
} else {
|
||||
columnSize = maxColumnSizePortrait;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -138,12 +149,14 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
|
|||
this,
|
||||
preferences,
|
||||
mContainerActivity,
|
||||
viewThemeUtils);
|
||||
viewThemeUtils,
|
||||
columnSize,
|
||||
ThumbnailsCacheManager.getThumbnailDimension());
|
||||
|
||||
setRecyclerViewAdapter(mAdapter);
|
||||
|
||||
|
||||
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), getColumnsCount());
|
||||
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 1);
|
||||
mAdapter.setLayoutManager(layoutManager);
|
||||
getRecyclerView().setLayoutManager(layoutManager);
|
||||
|
||||
|
@ -152,6 +165,23 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
|
|||
new GalleryFastScrollViewHelper(getRecyclerView(), mAdapter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
columnSize = maxColumnSizeLandscape;
|
||||
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
columnSize = maxColumnSizePortrait;
|
||||
}
|
||||
mAdapter.changeColumn(columnSize);
|
||||
showAllGalleryItems();
|
||||
}
|
||||
|
||||
public int getColumnsCount() {
|
||||
return columnSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
super.onRefresh();
|
||||
|
@ -236,7 +266,7 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
|
|||
|
||||
startDate = endDate - (daySpan * 24 * 60 * 60);
|
||||
|
||||
runGallerySearchTask();
|
||||
// runGallerySearchTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -360,4 +390,9 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setGridViewColumns(float scaleFactor) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ class GalleryFastScrollViewHelper(
|
|||
val adapter = mView.adapter as GalleryAdapter
|
||||
if (adapter.sectionCount == 0) return 0
|
||||
// in each section, the final row may contain less than the max of items
|
||||
return adapter.files.sumOf { itemCountToRowCount(it.files.size) }
|
||||
return adapter.files.sumOf { itemCountToRowCount(it.rows.size) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,7 +141,7 @@ class GalleryFastScrollViewHelper(
|
|||
|
||||
val seenRowsInPreviousSections = adapter.files
|
||||
.subList(0, min(itemCoord.section(), adapter.files.size))
|
||||
.sumOf { itemCountToRowCount(it.files.size) }
|
||||
.sumOf { itemCountToRowCount(it.rows.size) }
|
||||
val seenRowsInThisSection = if (isHeader) 0 else itemCountToRowCount(itemCoord.relativePos())
|
||||
val totalSeenRows = seenRowsInPreviousSections + seenRowsInThisSection
|
||||
|
||||
|
@ -209,7 +209,7 @@ class GalleryFastScrollViewHelper(
|
|||
*/
|
||||
private fun getSectionStartOffsets(files: List<GalleryItems>): List<Int> {
|
||||
val sectionHeights =
|
||||
files.map { headerHeight + itemCountToRowCount(it.files.size) * rowHeight }
|
||||
files.map { headerHeight + itemCountToRowCount(it.rows.size) * rowHeight }
|
||||
val sectionStartOffsets = sectionHeights.indices.map { i ->
|
||||
when (i) {
|
||||
0 -> 0
|
||||
|
|
|
@ -378,7 +378,8 @@ public final class BitmapUtils {
|
|||
return drawableToBitmap(drawable, -1, -1);
|
||||
}
|
||||
|
||||
public static Bitmap drawableToBitmap(Drawable drawable, int desiredWidth, int desiredHeight) {
|
||||
public static @NonNull
|
||||
Bitmap drawableToBitmap(Drawable drawable, int desiredWidth, int desiredHeight) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
|
||||
if (bitmapDrawable.getBitmap() != null) {
|
||||
|
|
|
@ -34,7 +34,9 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
@ -74,6 +76,7 @@ import com.owncloud.android.lib.common.OwnCloudAccount;
|
|||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.ui.TextDrawable;
|
||||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
||||
import com.owncloud.android.ui.adapter.GalleryRowHolder;
|
||||
import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
|
||||
import com.owncloud.android.ui.events.SearchEvent;
|
||||
import com.owncloud.android.ui.fragment.OCFileListFragment;
|
||||
|
@ -752,6 +755,13 @@ public final class DisplayUtils {
|
|||
return (int) (dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
|
||||
}
|
||||
|
||||
public static float convertPixelToDp(int px, Context context) {
|
||||
Resources resources = context.getResources();
|
||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
|
||||
return px * (DisplayMetrics.DENSITY_DEFAULT / (float) metrics.densityDpi);
|
||||
}
|
||||
|
||||
static public void showServerOutdatedSnackbar(Activity activity, int length) {
|
||||
Snackbar.make(activity.findViewById(android.R.id.content),
|
||||
R.string.outdated_server, length)
|
||||
|
@ -811,13 +821,109 @@ public final class DisplayUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getDateByPattern(long timestamp, Context context, String pattern) {
|
||||
DateFormat df = new SimpleDateFormat(pattern, context.getResources().getConfiguration().locale);
|
||||
public static String getDateByPattern(long timestamp, String pattern) {
|
||||
return getDateByPattern(timestamp, null, pattern);
|
||||
}
|
||||
|
||||
public static String getDateByPattern(long timestamp, @Nullable Context context, String pattern) {
|
||||
DateFormat df;
|
||||
if (context == null) {
|
||||
context = MainApp.getAppContext();
|
||||
}
|
||||
df = new SimpleDateFormat(pattern, context.getResources().getConfiguration().locale);
|
||||
df.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
|
||||
|
||||
return df.format(timestamp);
|
||||
}
|
||||
|
||||
public static void setGalleryImage(OCFile file,
|
||||
ImageView thumbnailView,
|
||||
User user,
|
||||
FileDataStorageManager storageManager,
|
||||
List<ThumbnailsCacheManager.GalleryImageGenerationTask> asyncTasks,
|
||||
boolean gridView,
|
||||
Context context,
|
||||
LoaderImageView shimmerThumbnail,
|
||||
AppPreferences preferences,
|
||||
ThemeColorUtils themeColorUtils,
|
||||
ThemeDrawableUtils themeDrawableUtils,
|
||||
GalleryRowHolder galleryRowHolder,
|
||||
Integer width) {
|
||||
|
||||
// cancel previous generation, if view is re-used
|
||||
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailView)) {
|
||||
for (ThumbnailsCacheManager.GalleryImageGenerationTask task : asyncTasks) {
|
||||
if (file.getRemoteId() != null && task.getImageKey() != null &&
|
||||
file.getRemoteId().equals(task.getImageKey())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
final ThumbnailsCacheManager.GalleryImageGenerationTask task =
|
||||
new ThumbnailsCacheManager.GalleryImageGenerationTask(
|
||||
thumbnailView,
|
||||
user,
|
||||
storageManager,
|
||||
asyncTasks,
|
||||
file.getRemoteId(),
|
||||
context.getResources().getColor(R.color.bg_default));
|
||||
Drawable drawable = MimeTypeUtil.getFileTypeIcon(file.getMimeType(),
|
||||
file.getFileName(),
|
||||
user,
|
||||
context,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils);
|
||||
if (drawable == null) {
|
||||
drawable = ResourcesCompat.getDrawable(context.getResources(),
|
||||
R.drawable.file_image,
|
||||
null);
|
||||
}
|
||||
|
||||
if (drawable == null) {
|
||||
drawable = new ColorDrawable(Color.GRAY);
|
||||
}
|
||||
|
||||
Bitmap thumbnail = BitmapUtils.drawableToBitmap(drawable, width / 2, width / 2);
|
||||
|
||||
final ThumbnailsCacheManager.AsyncGalleryImageDrawable asyncDrawable =
|
||||
new ThumbnailsCacheManager.AsyncGalleryImageDrawable(context.getResources(),
|
||||
thumbnail,
|
||||
task);
|
||||
|
||||
if (shimmerThumbnail != null) {
|
||||
Log_OC.d("Shimmer", "start Shimmer");
|
||||
startShimmer(shimmerThumbnail, thumbnailView);
|
||||
}
|
||||
|
||||
task.setListener(new ThumbnailsCacheManager.GalleryImageGenerationTask.GalleryListener() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
galleryRowHolder.getBinding().rowLayout.invalidate();
|
||||
Log_OC.d("Shimmer", "stop Shimmer");
|
||||
stopShimmer(shimmerThumbnail, thumbnailView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewGalleryImage() {
|
||||
galleryRowHolder.redraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
Log_OC.d("Shimmer", "stop Shimmer");
|
||||
stopShimmer(shimmerThumbnail, thumbnailView);
|
||||
}
|
||||
});
|
||||
|
||||
thumbnailView.setImageDrawable(asyncDrawable);
|
||||
asyncTasks.add(task);
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
file);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log_OC.d(TAG, "ThumbnailGenerationTask : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setThumbnail(OCFile file,
|
||||
ImageView thumbnailView,
|
||||
|
@ -850,7 +956,7 @@ public final class DisplayUtils {
|
|||
stopShimmer(shimmerThumbnail, thumbnailView);
|
||||
|
||||
if (MimeTypeUtil.isVideo(file)) {
|
||||
Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail);
|
||||
Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail, context);
|
||||
thumbnailView.setImageBitmap(withOverlay);
|
||||
} else {
|
||||
if (gridView) {
|
||||
|
@ -886,6 +992,10 @@ public final class DisplayUtils {
|
|||
R.drawable.file_image,
|
||||
null);
|
||||
}
|
||||
if (drawable == null) {
|
||||
drawable = new ColorDrawable(Color.GRAY);
|
||||
}
|
||||
|
||||
int px = ThumbnailsCacheManager.getThumbnailDimension();
|
||||
thumbnail = BitmapUtils.drawableToBitmap(drawable, px, px);
|
||||
}
|
||||
|
|
10
app/src/main/res/drawable/video_white.xml
Normal file
10
app/src/main/res/drawable/video_white.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!-- drawable/video.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17,10.5V7A1,1 0 0,0 16,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16A1,1 0 0,0 17,17V13.5L21,17.5V6.5L17,10.5Z" />
|
||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="11dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="11"
|
||||
android:viewportHeight="14">
|
||||
<path
|
||||
android:pathData="M10.9799,6.9947L-0.0057,13.967L0.0004,0.0153L10.9799,6.9947Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
|
@ -33,7 +33,8 @@
|
|||
android:id="@id/exo_prev"
|
||||
style="@style/FullScreenExoControlButton"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/exo_controls_previous" />
|
||||
android:src="@drawable/exo_controls_previous"
|
||||
android:contentDescription="@string/exo_controls_previous_description" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@id/exo_rew"
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/standard_margin"
|
||||
android:layout_marginTop="@dimen/standard_margin"
|
||||
android:layout_marginBottom="@dimen/standard_margin"
|
||||
android:layout_marginEnd="@dimen/standard_half_margin"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textStyle="bold"
|
||||
|
@ -43,6 +44,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/standard_margin"
|
||||
android:layout_marginBottom="@dimen/standard_margin"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
tools:text="2016" />
|
||||
</LinearLayout>
|
||||
|
|
27
app/src/main/res/layout/gallery_row.xml
Normal file
27
app/src/main/res/layout/gallery_row.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~
|
||||
~ Nextcloud Android client application
|
||||
~
|
||||
~ @author Tobias Kaminsky
|
||||
~ Copyright (C) 2022 Tobias Kaminsky
|
||||
~ Copyright (C) 2022 Nextcloud GmbH
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU Affero General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU Affero General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/row_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" />
|
|
@ -15,87 +15,12 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/ListItemLayout"
|
||||
android:layout_width="match_parent"
|
||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
android:layout_margin="10dp"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/file_image" />
|
||||
|
||||
<com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
android:id="@+id/thumbnail_shimmer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/grid_image_icon_margin"
|
||||
android:contentDescription="@null"
|
||||
android:visibility="gone"
|
||||
app:corners="6"
|
||||
app:height_weight="0.6"
|
||||
app:width_weight="0.4" />
|
||||
|
||||
<com.owncloud.android.ui.SquareImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:padding="@dimen/grid_image_icon_padding"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/file_image" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/favorite_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="@dimen/standard_quarter_margin"
|
||||
android:contentDescription="@string/favorite_icon"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/favorite" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sharedIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="@dimen/grid_image_shared_icon_layout_top_margin"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:contentDescription="@string/shared_icon_shared_via_link"
|
||||
android:src="@drawable/shared_via_link" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/unreadComments"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="@dimen/grid_image_shared_icon_layout_top_margin"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/unread_comments"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_comment_grid"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/localFileIndicator"
|
||||
android:layout_width="@dimen/grid_image_local_file_indicator_layout_width"
|
||||
android:layout_height="@dimen/grid_image_local_file_indicator_layout_height"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginTop="@dimen/standard_quarter_margin"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:layout_marginBottom="@dimen/standard_quarter_margin"
|
||||
android:contentDescription="@string/synced_icon"
|
||||
android:src="@drawable/ic_synced" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/custom_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|top"
|
||||
android:layout_marginLeft="@dimen/standard_quarter_margin"
|
||||
android:layout_marginRight="@dimen/standard_quarter_margin"
|
||||
android:contentDescription="@string/checkbox"
|
||||
android:src="@android:drawable/checkbox_off_background" />
|
||||
</FrameLayout>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
-->
|
||||
<com.owncloud.android.ui.SquareLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/ListItemLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -55,13 +56,30 @@
|
|||
android:visibility="gone"
|
||||
app:corners="8" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/folder" />
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/folder" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/videoOverlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:src="@drawable/video_white"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:contentDescription="@string/video_overlay_icon" />
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sharedIcon"
|
||||
|
@ -71,7 +89,7 @@
|
|||
android:layout_marginTop="@dimen/grid_item_shared_icon_layout_top_margin"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:src="@drawable/shared_via_link"
|
||||
android:contentDescription="@string/shared_icon_shared_via_link"/>
|
||||
android:contentDescription="@string/shared_icon_shared_via_link" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/unreadComments"
|
||||
|
|
|
@ -42,12 +42,29 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/file_icon_size"
|
||||
android:layout_height="@dimen/file_icon_size"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/folder" />
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/file_icon_size"
|
||||
android:layout_height="@dimen/file_icon_size"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/folder" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/videoOverlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:src="@drawable/video_white"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:contentDescription="@string/video_overlay_icon" />
|
||||
</FrameLayout>
|
||||
|
||||
<com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
android:id="@+id/thumbnail_shimmer"
|
||||
|
|
|
@ -106,11 +106,6 @@
|
|||
<dimen name="contactlist_item_icon_layout_height">40dp</dimen>
|
||||
<dimen name="empty_list_icon_layout_width">72dp</dimen>
|
||||
<dimen name="empty_list_icon_layout_height">72dp</dimen>
|
||||
<dimen name="grid_image_shared_icon_layout_top_margin">24dp</dimen>
|
||||
<dimen name="grid_image_local_file_indicator_layout_width">16dp</dimen>
|
||||
<dimen name="grid_image_local_file_indicator_layout_height">16dp</dimen>
|
||||
<dimen name="grid_image_icon_margin">14dp</dimen>
|
||||
<dimen name="grid_image_icon_padding">14dp</dimen>
|
||||
<dimen name="grid_item_shared_icon_layout_top_margin">24dp</dimen>
|
||||
<dimen name="grid_item_local_file_indicator_layout_width">16dp</dimen>
|
||||
<dimen name="grid_item_local_file_indicator_layout_height">16dp</dimen>
|
||||
|
@ -145,5 +140,6 @@
|
|||
<dimen name="default_login_width">400dp</dimen>
|
||||
<dimen name="dialogBorderRadius">24dp</dimen>
|
||||
<dimen name="dialog_padding">24dp</dimen>
|
||||
|
||||
<integer name="small_margin">5</integer>
|
||||
<integer name="zero">0</integer>
|
||||
</resources>
|
||||
|
|
|
@ -1041,6 +1041,7 @@
|
|||
<string name="file_already_exists">Filename already exists</string>
|
||||
<string name="filedetails_export">Export</string>
|
||||
<string name="locate_folder">Locate folder</string>
|
||||
<string name="video_overlay_icon">video overlay icon</string>
|
||||
<string name="app_widget_description">Shows one widget from dashboard</string>
|
||||
<string name="icon_of_dashboard_widget">Icon of dashboard widget</string>
|
||||
<string name="refresh_content">Refresh content</string>
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.nextcloud.client.account.User
|
|||
import com.nextcloud.client.preferences.AppPreferences
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.datamodel.GalleryItems
|
||||
import com.owncloud.android.datamodel.GalleryRow
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import com.owncloud.android.ui.activity.ComponentsGetter
|
||||
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface
|
||||
|
@ -77,6 +78,7 @@ class GalleryAdapterTest {
|
|||
@Test
|
||||
fun testItemCount() {
|
||||
whenever(transferServiceGetter.storageManager) doReturn storageManager
|
||||
val thumbnailSize = 50
|
||||
|
||||
val sut = GalleryAdapter(
|
||||
context,
|
||||
|
@ -84,16 +86,24 @@ class GalleryAdapterTest {
|
|||
ocFileListFragmentInterface,
|
||||
preferences,
|
||||
transferServiceGetter,
|
||||
viewThemeUtils
|
||||
viewThemeUtils,
|
||||
5,
|
||||
thumbnailSize
|
||||
)
|
||||
|
||||
val list = listOf(
|
||||
GalleryItems(1649317247, listOf(OCFile("/1.md"), OCFile("/2.md"))),
|
||||
GalleryItems(1649317247, listOf(OCFile("/1.md"), OCFile("/2.md")))
|
||||
GalleryItems(
|
||||
1649317247,
|
||||
listOf(GalleryRow(listOf(OCFile("/1.md"), OCFile("/2.md")), thumbnailSize, thumbnailSize))
|
||||
),
|
||||
GalleryItems(
|
||||
1649317248,
|
||||
listOf(GalleryRow(listOf(OCFile("/1.md"), OCFile("/2.md")), thumbnailSize, thumbnailSize))
|
||||
)
|
||||
)
|
||||
|
||||
sut.addFiles(list)
|
||||
|
||||
assertEquals(4, sut.getFilesCount())
|
||||
assertEquals(2, sut.getFilesCount())
|
||||
}
|
||||
}
|
||||
|
|
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,6 @@
|
|||
#Wed Oct 12 12:37:36 CEST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
Loading…
Reference in a new issue