multipage: open with default track
This commit is contained in:
parent
60243a20fd
commit
a6b99e7c2a
24 changed files with 163 additions and 143 deletions
|
@ -58,7 +58,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
|||
val isFlipped = call.argument<Boolean>("isFlipped")
|
||||
val widthDip = call.argument<Double>("widthDip")
|
||||
val heightDip = call.argument<Double>("heightDip")
|
||||
val page = call.argument<Int>("page")
|
||||
val pageId = call.argument<Int>("pageId")
|
||||
val defaultSizeDip = call.argument<Double>("defaultSizeDip")
|
||||
|
||||
if (uri == null || mimeType == null || dateModifiedSecs == null || rotationDegrees == null || isFlipped == null || widthDip == null || heightDip == null || defaultSizeDip == null) {
|
||||
|
@ -76,7 +76,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
|||
isFlipped,
|
||||
width = (widthDip * density).roundToInt(),
|
||||
height = (heightDip * density).roundToInt(),
|
||||
page = page,
|
||||
pageId = pageId,
|
||||
defaultSize = (defaultSizeDip * density).roundToInt(),
|
||||
result,
|
||||
).fetch()
|
||||
|
@ -85,7 +85,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
|||
private fun getRegion(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||
val mimeType = call.argument<String>("mimeType")
|
||||
val page = call.argument<Int>("page")
|
||||
val pageId = call.argument<Int>("pageId")
|
||||
val sampleSize = call.argument<Int>("sampleSize")
|
||||
val x = call.argument<Int>("regionX")
|
||||
val y = call.argument<Int>("regionY")
|
||||
|
@ -105,7 +105,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
|||
uri,
|
||||
sampleSize,
|
||||
regionRect,
|
||||
page = page ?: 0,
|
||||
page = pageId ?: 0,
|
||||
result,
|
||||
)
|
||||
else -> regionFetcher.fetch(
|
||||
|
|
|
@ -524,20 +524,21 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
|||
return
|
||||
}
|
||||
|
||||
val pages = HashMap<Int, Any>()
|
||||
val pages = ArrayList<Map<String, Any>>()
|
||||
if (mimeType == MimeTypes.TIFF) {
|
||||
fun toMap(options: TiffBitmapFactory.Options): Map<String, Any> {
|
||||
fun toMap(page: Int, options: TiffBitmapFactory.Options): HashMap<String, Any> {
|
||||
return hashMapOf(
|
||||
KEY_PAGE to page,
|
||||
KEY_MIME_TYPE to mimeType,
|
||||
KEY_WIDTH to options.outWidth,
|
||||
KEY_HEIGHT to options.outHeight,
|
||||
)
|
||||
}
|
||||
getTiffPageInfo(uri, 0)?.let { first ->
|
||||
pages[0] = toMap(first)
|
||||
pages.add(toMap(0, first))
|
||||
val pageCount = first.outDirectoryCount
|
||||
for (i in 1 until pageCount) {
|
||||
getTiffPageInfo(uri, i)?.let { pages[i] = toMap(it) }
|
||||
getTiffPageInfo(uri, i)?.let { pages.add(toMap(i, it)) }
|
||||
}
|
||||
}
|
||||
} else if (isHeifLike(mimeType)) {
|
||||
|
@ -556,14 +557,18 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
|||
val format = extractor.getTrackFormat(i)
|
||||
format.getString(MediaFormat.KEY_MIME)?.let { mime ->
|
||||
val trackMime = if (mime == MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC) MimeTypes.HEIC else mime
|
||||
val page = hashMapOf<String, Any>(KEY_MIME_TYPE to trackMime)
|
||||
val page = hashMapOf<String, Any>(
|
||||
KEY_PAGE to i,
|
||||
KEY_MIME_TYPE to trackMime,
|
||||
)
|
||||
format.getSafeInt(MediaFormat.KEY_IS_DEFAULT) { page[KEY_IS_DEFAULT] = it != 0 }
|
||||
format.getSafeInt(MediaFormat.KEY_TRACK_ID) { page[KEY_TRACK_ID] = it }
|
||||
format.getSafeInt(MediaFormat.KEY_WIDTH) { page[KEY_WIDTH] = it }
|
||||
format.getSafeInt(MediaFormat.KEY_HEIGHT) { page[KEY_HEIGHT] = it }
|
||||
if (isVideo(trackMime)) {
|
||||
format.getSafeLong(MediaFormat.KEY_DURATION) { page[KEY_DURATION] = it / 1000 }
|
||||
}
|
||||
pages[i] = page
|
||||
pages.add(page)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get track information for uri=$uri, track num=$i", e)
|
||||
|
@ -778,7 +783,9 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
|||
private const val KEY_XMP_TITLE_DESCRIPTION = "xmpTitleDescription"
|
||||
private const val KEY_HEIGHT = "height"
|
||||
private const val KEY_WIDTH = "width"
|
||||
private const val KEY_PAGE = "page"
|
||||
private const val KEY_TRACK_ID = "trackId"
|
||||
private const val KEY_IS_DEFAULT = "isDefault"
|
||||
private const val KEY_DURATION = "durationMillis"
|
||||
|
||||
private const val MASK_IS_ANIMATED = 1 shl 0
|
||||
|
|
|
@ -34,7 +34,7 @@ class ThumbnailFetcher internal constructor(
|
|||
private val isFlipped: Boolean,
|
||||
width: Int?,
|
||||
height: Int?,
|
||||
private val page: Int?,
|
||||
private val pageId: Int?,
|
||||
private val defaultSize: Int,
|
||||
private val result: MethodChannel.Result,
|
||||
) {
|
||||
|
@ -42,7 +42,7 @@ class ThumbnailFetcher internal constructor(
|
|||
private val width: Int = if (width?.takeIf { it > 0 } != null) width else defaultSize
|
||||
private val height: Int = if (height?.takeIf { it > 0 } != null) height else defaultSize
|
||||
private val tiffFetch = mimeType == MimeTypes.TIFF
|
||||
private val multiTrackFetch = isHeifLike(mimeType) && page != null
|
||||
private val multiTrackFetch = isHeifLike(mimeType) && pageId != null
|
||||
private val customFetch = tiffFetch || multiTrackFetch
|
||||
|
||||
fun fetch() {
|
||||
|
@ -114,7 +114,7 @@ class ThumbnailFetcher internal constructor(
|
|||
// add signature to ignore cache for images which got modified but kept the same URI
|
||||
var options = RequestOptions()
|
||||
.format(DecodeFormat.PREFER_RGB_565)
|
||||
.signature(ObjectKey("$dateModifiedSecs-$rotationDegrees-$isFlipped-$width-$page"))
|
||||
.signature(ObjectKey("$dateModifiedSecs-$rotationDegrees-$isFlipped-$width-$pageId"))
|
||||
.override(width, height)
|
||||
|
||||
val target = if (isVideo(mimeType)) {
|
||||
|
@ -125,11 +125,11 @@ class ThumbnailFetcher internal constructor(
|
|||
.load(VideoThumbnail(context, uri))
|
||||
.submit(width, height)
|
||||
} else {
|
||||
val model: Any = if (tiffFetch) {
|
||||
TiffThumbnail(context, uri, page ?: 0)
|
||||
} else if (multiTrackFetch) {
|
||||
MultiTrackImage(context, uri, page ?: 0)
|
||||
} else uri
|
||||
val model: Any = when {
|
||||
tiffFetch -> TiffThumbnail(context, uri, pageId ?: 0)
|
||||
multiTrackFetch -> MultiTrackImage(context, uri, pageId)
|
||||
else -> uri
|
||||
}
|
||||
Glide.with(context)
|
||||
.asBitmap()
|
||||
.apply(options)
|
||||
|
|
|
@ -86,7 +86,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
|
|||
val uri = (arguments["uri"] as String?)?.let { Uri.parse(it) }
|
||||
val rotationDegrees = arguments["rotationDegrees"] as Int
|
||||
val isFlipped = arguments["isFlipped"] as Boolean
|
||||
val page = arguments["page"] as Int?
|
||||
val pageId = arguments["pageId"] as Int?
|
||||
|
||||
if (mimeType == null || uri == null) {
|
||||
error("streamImage-args", "failed because of missing arguments", null)
|
||||
|
@ -97,10 +97,10 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
|
|||
if (isVideo(mimeType)) {
|
||||
streamVideoByGlide(uri)
|
||||
} else if (mimeType == MimeTypes.TIFF) {
|
||||
streamTiffImage(uri, page)
|
||||
streamTiffImage(uri, pageId)
|
||||
} else if (!isSupportedByFlutter(mimeType, rotationDegrees, isFlipped)) {
|
||||
// decode exotic format on platform side, then encode it in portable format for Flutter
|
||||
streamImageByGlide(uri, page, mimeType, rotationDegrees, isFlipped)
|
||||
streamImageByGlide(uri, pageId, mimeType, rotationDegrees, isFlipped)
|
||||
} else {
|
||||
// to be decoded by Flutter
|
||||
streamImageAsIs(uri)
|
||||
|
@ -116,9 +116,9 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
|
|||
}
|
||||
}
|
||||
|
||||
private fun streamImageByGlide(uri: Uri, page: Int?, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) {
|
||||
val model: Any = if (isHeifLike(mimeType) && page != null) {
|
||||
MultiTrackImage(activity, uri, page)
|
||||
private fun streamImageByGlide(uri: Uri, pageId: Int?, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) {
|
||||
val model: Any = if (isHeifLike(mimeType) && pageId != null) {
|
||||
MultiTrackImage(activity, uri, pageId)
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class MultiTrackImageGlideModule : LibraryGlideModule() {
|
|||
}
|
||||
}
|
||||
|
||||
class MultiTrackImage(val context: Context, val uri: Uri, val trackIndex: Int)
|
||||
class MultiTrackImage(val context: Context, val uri: Uri, val trackId: Int?)
|
||||
|
||||
internal class MultiTrackThumbnailLoader : ModelLoader<MultiTrackImage, InputStream> {
|
||||
override fun buildLoadData(model: MultiTrackImage, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream> {
|
||||
|
@ -53,9 +53,9 @@ internal class MultiTrackImageFetcher(val model: MultiTrackImage, val width: Int
|
|||
|
||||
val context = model.context
|
||||
val uri = model.uri
|
||||
val trackIndex = model.trackIndex
|
||||
val trackId = model.trackId
|
||||
|
||||
val bitmap = MultiTrackMedia.getImage(context, uri, trackIndex)
|
||||
val bitmap = MultiTrackMedia.getImage(context, uri, trackId)
|
||||
if (bitmap == null) {
|
||||
callback.onLoadFailed(Exception("null bitmap"))
|
||||
} else {
|
||||
|
|
|
@ -16,14 +16,17 @@ object MultiTrackMedia {
|
|||
private val LOG_TAG = LogUtils.createTag(MultiTrackMedia::class.java)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
fun getImage(context: Context, uri: Uri, trackIndex: Int): Bitmap? {
|
||||
val imageIndex = trackIndexToImageIndex(context, uri, trackIndex) ?: return null
|
||||
|
||||
fun getImage(context: Context, uri: Uri, trackId: Int?): Bitmap? {
|
||||
val retriever = StorageUtils.openMetadataRetriever(context, uri) ?: return null
|
||||
try {
|
||||
return retriever.getImageAtIndex(imageIndex)
|
||||
return if (trackId != null) {
|
||||
val imageIndex = trackIdToImageIndex(context, uri, trackId) ?: return null
|
||||
retriever.getImageAtIndex(imageIndex)
|
||||
} else {
|
||||
retriever.primaryImage
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to extract image from uri=$uri trackIndex=$trackIndex imageIndex=$imageIndex", e)
|
||||
Log.w(LOG_TAG, "failed to extract image from uri=$uri trackId=$trackId", e)
|
||||
} finally {
|
||||
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
|
||||
retriever.release()
|
||||
|
@ -31,21 +34,23 @@ object MultiTrackMedia {
|
|||
return null
|
||||
}
|
||||
|
||||
private fun trackIndexToImageIndex(context: Context, uri: Uri, trackIndex: Int): Int? {
|
||||
private fun trackIdToImageIndex(context: Context, uri: Uri, trackId: Int): Int? {
|
||||
val extractor = MediaExtractor()
|
||||
try {
|
||||
extractor.setDataSource(context, uri, null)
|
||||
val trackCount = extractor.trackCount
|
||||
if (trackIndex < trackCount) {
|
||||
var imageIndex = 0
|
||||
for (i in 0 until trackIndex) {
|
||||
val mimeType = extractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME)
|
||||
if (MimeTypes.isImage(mimeType)) imageIndex++
|
||||
var imageIndex = 0
|
||||
for (i in 0 until trackCount) {
|
||||
val trackFormat = extractor.getTrackFormat(i)
|
||||
if (trackId == trackFormat.getInteger(MediaFormat.KEY_TRACK_ID)) {
|
||||
return imageIndex
|
||||
}
|
||||
if (MimeTypes.isImage(trackFormat.getString(MediaFormat.KEY_MIME))) {
|
||||
imageIndex++
|
||||
}
|
||||
return imageIndex
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get image index for uri=$uri, trackIndex=$trackIndex", e)
|
||||
Log.w(LOG_TAG, "failed to get image index for uri=$uri, trackId=$trackId", e)
|
||||
} finally {
|
||||
extractor.release()
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class RegionProvider extends ImageProvider<RegionProviderKey> {
|
|||
codec: _loadAsync(key, decode),
|
||||
scale: key.scale,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription('uri=${key.uri}, page=${key.page}, mimeType=${key.mimeType}, region=${key.region}');
|
||||
yield ErrorDescription('uri=${key.uri}, pageId=${key.pageId}, mimeType=${key.mimeType}, region=${key.region}');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ class RegionProvider extends ImageProvider<RegionProviderKey> {
|
|||
Future<ui.Codec> _loadAsync(RegionProviderKey key, DecoderCallback decode) async {
|
||||
final uri = key.uri;
|
||||
final mimeType = key.mimeType;
|
||||
final page = key.page;
|
||||
final pageId = key.pageId;
|
||||
try {
|
||||
final bytes = await ImageFileService.getRegion(
|
||||
uri,
|
||||
|
@ -40,7 +40,7 @@ class RegionProvider extends ImageProvider<RegionProviderKey> {
|
|||
key.sampleSize,
|
||||
key.region,
|
||||
key.imageSize,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
taskKey: key,
|
||||
);
|
||||
if (bytes == null) {
|
||||
|
@ -49,7 +49,7 @@ class RegionProvider extends ImageProvider<RegionProviderKey> {
|
|||
return await decode(bytes);
|
||||
} catch (error) {
|
||||
debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error');
|
||||
throw StateError('$mimeType region decoding failed (page $page)');
|
||||
throw StateError('$mimeType region decoding failed (page $pageId)');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ class RegionProviderKey {
|
|||
// do not store the entry as it is, because the key should be constant
|
||||
// but the entry attributes may change over time
|
||||
final String uri, mimeType;
|
||||
final int page, rotationDegrees, sampleSize;
|
||||
final int pageId, rotationDegrees, sampleSize;
|
||||
final bool isFlipped;
|
||||
final Rectangle<int> region;
|
||||
final Size imageSize;
|
||||
|
@ -75,7 +75,7 @@ class RegionProviderKey {
|
|||
const RegionProviderKey({
|
||||
@required this.uri,
|
||||
@required this.mimeType,
|
||||
@required this.page,
|
||||
@required this.pageId,
|
||||
@required this.rotationDegrees,
|
||||
@required this.isFlipped,
|
||||
@required this.sampleSize,
|
||||
|
@ -94,14 +94,14 @@ class RegionProviderKey {
|
|||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is RegionProviderKey && other.uri == uri && other.mimeType == mimeType && other.page == page && other.rotationDegrees == rotationDegrees && other.isFlipped == isFlipped && other.sampleSize == sampleSize && other.region == region && other.imageSize == imageSize && other.scale == scale;
|
||||
return other is RegionProviderKey && other.uri == uri && other.mimeType == mimeType && other.pageId == pageId && other.rotationDegrees == rotationDegrees && other.isFlipped == isFlipped && other.sampleSize == sampleSize && other.region == region && other.imageSize == imageSize && other.scale == scale;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(
|
||||
uri,
|
||||
mimeType,
|
||||
page,
|
||||
pageId,
|
||||
rotationDegrees,
|
||||
isFlipped,
|
||||
sampleSize,
|
||||
|
@ -111,5 +111,5 @@ class RegionProviderKey {
|
|||
);
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, page=$page, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, sampleSize=$sampleSize, region=$region, imageSize=$imageSize, scale=$scale}';
|
||||
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, pageId=$pageId, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, sampleSize=$sampleSize, region=$region, imageSize=$imageSize, scale=$scale}';
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
|
|||
codec: _loadAsync(key, decode),
|
||||
scale: key.scale,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription('uri=${key.uri}, page=${key.page}, mimeType=${key.mimeType}, extent=${key.extent}');
|
||||
yield ErrorDescription('uri=${key.uri}, pageId=${key.pageId}, mimeType=${key.mimeType}, extent=${key.extent}');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -31,12 +31,12 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
|
|||
Future<ui.Codec> _loadAsync(ThumbnailProviderKey key, DecoderCallback decode) async {
|
||||
final uri = key.uri;
|
||||
final mimeType = key.mimeType;
|
||||
final page = key.page;
|
||||
final pageId = key.pageId;
|
||||
try {
|
||||
final bytes = await ImageFileService.getThumbnail(
|
||||
uri: uri,
|
||||
mimeType: mimeType,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
rotationDegrees: key.rotationDegrees,
|
||||
isFlipped: key.isFlipped,
|
||||
dateModifiedSecs: key.dateModifiedSecs,
|
||||
|
@ -49,7 +49,7 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
|
|||
return await decode(bytes);
|
||||
} catch (error) {
|
||||
debugPrint('$runtimeType _loadAsync failed with uri=$uri, error=$error');
|
||||
throw StateError('$mimeType decoding failed (page $page)');
|
||||
throw StateError('$mimeType decoding failed (page $pageId)');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ class ThumbnailProviderKey {
|
|||
// do not store the entry as it is, because the key should be constant
|
||||
// but the entry attributes may change over time
|
||||
final String uri, mimeType;
|
||||
final int page, rotationDegrees;
|
||||
final int pageId, rotationDegrees;
|
||||
final bool isFlipped;
|
||||
final int dateModifiedSecs;
|
||||
final double extent, scale;
|
||||
|
@ -74,7 +74,7 @@ class ThumbnailProviderKey {
|
|||
const ThumbnailProviderKey({
|
||||
@required this.uri,
|
||||
@required this.mimeType,
|
||||
@required this.page,
|
||||
@required this.pageId,
|
||||
@required this.rotationDegrees,
|
||||
@required this.isFlipped,
|
||||
@required this.dateModifiedSecs,
|
||||
|
@ -91,14 +91,14 @@ class ThumbnailProviderKey {
|
|||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is ThumbnailProviderKey && other.uri == uri && other.mimeType == mimeType && other.page == page && other.rotationDegrees == rotationDegrees && other.isFlipped == isFlipped && other.dateModifiedSecs == dateModifiedSecs && other.extent == extent && other.scale == scale;
|
||||
return other is ThumbnailProviderKey && other.uri == uri && other.mimeType == mimeType && other.pageId == pageId && other.rotationDegrees == rotationDegrees && other.isFlipped == isFlipped && other.dateModifiedSecs == dateModifiedSecs && other.extent == extent && other.scale == scale;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(
|
||||
uri,
|
||||
mimeType,
|
||||
page,
|
||||
pageId,
|
||||
rotationDegrees,
|
||||
isFlipped,
|
||||
dateModifiedSecs,
|
||||
|
@ -107,5 +107,5 @@ class ThumbnailProviderKey {
|
|||
);
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, page=$page, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, dateModifiedSecs=$dateModifiedSecs, extent=$extent, scale=$scale}';
|
||||
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, pageId=$pageId, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, dateModifiedSecs=$dateModifiedSecs, extent=$extent, scale=$scale}';
|
||||
}
|
||||
|
|
|
@ -8,14 +8,14 @@ import 'package:pedantic/pedantic.dart';
|
|||
|
||||
class UriImage extends ImageProvider<UriImage> {
|
||||
final String uri, mimeType;
|
||||
final int page, rotationDegrees, expectedContentLength;
|
||||
final int pageId, rotationDegrees, expectedContentLength;
|
||||
final bool isFlipped;
|
||||
final double scale;
|
||||
|
||||
const UriImage({
|
||||
@required this.uri,
|
||||
@required this.mimeType,
|
||||
@required this.page,
|
||||
@required this.pageId,
|
||||
@required this.rotationDegrees,
|
||||
@required this.isFlipped,
|
||||
this.expectedContentLength,
|
||||
|
@ -37,7 +37,7 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
scale: key.scale,
|
||||
chunkEvents: chunkEvents.stream,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription('uri=$uri, page=$page, mimeType=$mimeType');
|
||||
yield ErrorDescription('uri=$uri, pageId=$pageId, mimeType=$mimeType');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
mimeType,
|
||||
rotationDegrees,
|
||||
isFlipped,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
expectedContentLength: expectedContentLength,
|
||||
onBytesReceived: (cumulative, total) {
|
||||
chunkEvents.add(ImageChunkEvent(
|
||||
|
@ -66,7 +66,7 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
return await decode(bytes);
|
||||
} catch (error) {
|
||||
debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error');
|
||||
throw StateError('$mimeType decoding failed (page $page)');
|
||||
throw StateError('$mimeType decoding failed (page $pageId)');
|
||||
} finally {
|
||||
unawaited(chunkEvents.close());
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is UriImage && other.uri == uri && other.mimeType == mimeType && other.rotationDegrees == rotationDegrees && other.isFlipped == isFlipped && other.page == page && other.scale == scale;
|
||||
return other is UriImage && other.uri == uri && other.mimeType == mimeType && other.rotationDegrees == rotationDegrees && other.isFlipped == isFlipped && other.pageId == pageId && other.scale == scale;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -84,10 +84,10 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
mimeType,
|
||||
rotationDegrees,
|
||||
isFlipped,
|
||||
page,
|
||||
pageId,
|
||||
scale,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, page=$page, scale=$scale}';
|
||||
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, pageId=$pageId, scale=$scale}';
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import '../ref/mime_types.dart';
|
|||
class AvesEntry {
|
||||
String uri;
|
||||
String _path, _directory, _filename, _extension;
|
||||
int page, contentId;
|
||||
int pageId, contentId;
|
||||
final String sourceMimeType;
|
||||
int width;
|
||||
int height;
|
||||
|
@ -49,7 +49,7 @@ class AvesEntry {
|
|||
this.uri,
|
||||
String path,
|
||||
this.contentId,
|
||||
this.page,
|
||||
this.pageId,
|
||||
this.sourceMimeType,
|
||||
@required this.width,
|
||||
@required this.height,
|
||||
|
@ -96,18 +96,14 @@ class AvesEntry {
|
|||
return copied;
|
||||
}
|
||||
|
||||
AvesEntry getPageEntry({
|
||||
@required MultiPageInfo multiPageInfo,
|
||||
@required int page,
|
||||
}) {
|
||||
final pageInfo = (multiPageInfo?.pages ?? {})[page];
|
||||
AvesEntry getPageEntry(SinglePageInfo pageInfo) {
|
||||
if (pageInfo == null) return this;
|
||||
return AvesPageEntry(
|
||||
pageInfo: pageInfo,
|
||||
uri: uri,
|
||||
path: path,
|
||||
contentId: contentId,
|
||||
page: page,
|
||||
pageId: pageInfo.pageId,
|
||||
sourceMimeType: sourceMimeType,
|
||||
width: width,
|
||||
height: height,
|
||||
|
@ -168,7 +164,7 @@ class AvesEntry {
|
|||
}
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, path=$path}';
|
||||
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, path=$path, pageId=$pageId}';
|
||||
|
||||
set path(String path) {
|
||||
_path = path;
|
||||
|
@ -227,7 +223,7 @@ class AvesEntry {
|
|||
MimeTypes.srw,
|
||||
].contains(mimeType) &&
|
||||
!isAnimated &&
|
||||
page == null;
|
||||
pageId == null;
|
||||
|
||||
bool get supportTiling => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff;
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@ class EntryCache {
|
|||
int oldRotationDegrees,
|
||||
bool oldIsFlipped,
|
||||
) async {
|
||||
// TODO TLAD provide page parameter for multipage items, if someday image editing features are added for them
|
||||
int page;
|
||||
// TODO TLAD provide pageId parameter for multipage items, if someday image editing features are added for them
|
||||
int pageId;
|
||||
|
||||
// evict fullscreen image
|
||||
await UriImage(
|
||||
uri: uri,
|
||||
mimeType: mimeType,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
rotationDegrees: oldRotationDegrees,
|
||||
isFlipped: oldIsFlipped,
|
||||
).evict();
|
||||
|
@ -28,7 +28,7 @@ class EntryCache {
|
|||
await ThumbnailProvider(ThumbnailProviderKey(
|
||||
uri: uri,
|
||||
mimeType: mimeType,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
dateModifiedSecs: dateModifiedSecs,
|
||||
rotationDegrees: oldRotationDegrees,
|
||||
isFlipped: oldIsFlipped,
|
||||
|
@ -41,7 +41,7 @@ class EntryCache {
|
|||
(extent) => ThumbnailProvider(ThumbnailProviderKey(
|
||||
uri: uri,
|
||||
mimeType: mimeType,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
dateModifiedSecs: dateModifiedSecs,
|
||||
rotationDegrees: oldRotationDegrees,
|
||||
isFlipped: oldIsFlipped,
|
||||
|
|
|
@ -20,7 +20,7 @@ extension ExtraAvesEntry on AvesEntry {
|
|||
return ThumbnailProviderKey(
|
||||
uri: uri,
|
||||
mimeType: mimeType,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
rotationDegrees: rotationDegrees,
|
||||
isFlipped: isFlipped,
|
||||
dateModifiedSecs: dateModifiedSecs ?? -1,
|
||||
|
@ -34,7 +34,7 @@ extension ExtraAvesEntry on AvesEntry {
|
|||
return RegionProviderKey(
|
||||
uri: uri,
|
||||
mimeType: mimeType,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
rotationDegrees: rotationDegrees,
|
||||
isFlipped: isFlipped,
|
||||
sampleSize: sampleSize,
|
||||
|
@ -46,7 +46,7 @@ extension ExtraAvesEntry on AvesEntry {
|
|||
UriImage get uriImage => UriImage(
|
||||
uri: uri,
|
||||
mimeType: mimeType,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
rotationDegrees: rotationDegrees,
|
||||
isFlipped: isFlipped,
|
||||
expectedContentLength: sizeBytes,
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class MultiPageInfo {
|
||||
final Map<int, SinglePageInfo> pages;
|
||||
final List<SinglePageInfo> pages;
|
||||
|
||||
int get pageCount => pages.length;
|
||||
|
||||
|
@ -10,44 +10,49 @@ class MultiPageInfo {
|
|||
this.pages,
|
||||
});
|
||||
|
||||
factory MultiPageInfo.fromMap(Map map) {
|
||||
final pages = <int, SinglePageInfo>{};
|
||||
map.keys.forEach((key) {
|
||||
final index = key as int;
|
||||
pages.putIfAbsent(index, () => SinglePageInfo.fromMap(map[key]));
|
||||
});
|
||||
return MultiPageInfo(pages: pages);
|
||||
factory MultiPageInfo.fromPageMaps(List<Map> pageMaps) {
|
||||
return MultiPageInfo(pages: pageMaps.map((page) => SinglePageInfo.fromMap(page)).toList());
|
||||
}
|
||||
|
||||
SinglePageInfo getByIndex(int index) => pages.firstWhere((page) => page.index == index, orElse: () => null);
|
||||
|
||||
SinglePageInfo getById(int pageId) => pages.firstWhere((page) => page.pageId == pageId, orElse: () => null);
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType#${shortHash(this)}{pages=$pages}';
|
||||
}
|
||||
|
||||
class SinglePageInfo {
|
||||
final int index, pageId;
|
||||
final String mimeType;
|
||||
final int width, height;
|
||||
final int trackId, durationMillis;
|
||||
final bool isDefault;
|
||||
final int width, height, durationMillis;
|
||||
|
||||
SinglePageInfo({
|
||||
this.index,
|
||||
this.pageId,
|
||||
this.mimeType,
|
||||
this.isDefault,
|
||||
this.width,
|
||||
this.height,
|
||||
this.trackId,
|
||||
this.durationMillis,
|
||||
});
|
||||
|
||||
factory SinglePageInfo.fromMap(Map map) {
|
||||
final index = map['page'] as int;
|
||||
return SinglePageInfo(
|
||||
index: index,
|
||||
pageId: map['trackId'] as int ?? index,
|
||||
mimeType: map['mimeType'] as String,
|
||||
width: map['width'] as int,
|
||||
height: map['height'] as int,
|
||||
trackId: map['trackId'] as int,
|
||||
isDefault: map['isDefault'] as bool ?? false,
|
||||
width: map['width'] as int ?? 0,
|
||||
height: map['height'] as int ?? 0,
|
||||
durationMillis: map['durationMillis'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType#${shortHash(this)}{mimeType=$mimeType, width=$width, height=$height, trackId=$trackId, durationMillis=$durationMillis}';
|
||||
String toString() => '$runtimeType#${shortHash(this)}{index=$index, pageId=$pageId, mimeType=$mimeType, isDefault=$isDefault, width=$width, height=$height, durationMillis=$durationMillis}';
|
||||
}
|
||||
|
||||
class AvesPageEntry extends AvesEntry {
|
||||
|
@ -58,7 +63,7 @@ class AvesPageEntry extends AvesEntry {
|
|||
String uri,
|
||||
String path,
|
||||
int contentId,
|
||||
int page,
|
||||
int pageId,
|
||||
String sourceMimeType,
|
||||
int width,
|
||||
int height,
|
||||
|
@ -72,7 +77,7 @@ class AvesPageEntry extends AvesEntry {
|
|||
uri: uri,
|
||||
path: path,
|
||||
contentId: contentId,
|
||||
page: page,
|
||||
pageId: pageId,
|
||||
sourceMimeType: pageInfo.mimeType ?? sourceMimeType,
|
||||
width: pageInfo.width ?? width,
|
||||
height: pageInfo.height ?? height,
|
||||
|
|
|
@ -33,7 +33,7 @@ class AppShortcutService {
|
|||
iconBytes = await ImageFileService.getThumbnail(
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
page: entry.page,
|
||||
pageId: entry.pageId,
|
||||
rotationDegrees: entry.rotationDegrees,
|
||||
isFlipped: entry.isFlipped,
|
||||
dateModifiedSecs: entry.dateModifiedSecs,
|
||||
|
|
|
@ -89,7 +89,7 @@ class ImageFileService {
|
|||
String mimeType,
|
||||
int rotationDegrees,
|
||||
bool isFlipped, {
|
||||
int page,
|
||||
int pageId,
|
||||
int expectedContentLength,
|
||||
BytesReceivedCallback onBytesReceived,
|
||||
}) {
|
||||
|
@ -102,7 +102,7 @@ class ImageFileService {
|
|||
'mimeType': mimeType,
|
||||
'rotationDegrees': rotationDegrees ?? 0,
|
||||
'isFlipped': isFlipped ?? false,
|
||||
'page': page,
|
||||
'pageId': pageId,
|
||||
}).listen(
|
||||
(data) {
|
||||
final chunk = data as Uint8List;
|
||||
|
@ -140,7 +140,7 @@ class ImageFileService {
|
|||
int sampleSize,
|
||||
Rectangle<int> regionRect,
|
||||
Size imageSize, {
|
||||
int page,
|
||||
int pageId,
|
||||
Object taskKey,
|
||||
int priority,
|
||||
}) {
|
||||
|
@ -150,7 +150,7 @@ class ImageFileService {
|
|||
final result = await platform.invokeMethod('getRegion', <String, dynamic>{
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
'page': page,
|
||||
'pageId': pageId,
|
||||
'sampleSize': sampleSize,
|
||||
'regionX': regionRect.left,
|
||||
'regionY': regionRect.top,
|
||||
|
@ -174,7 +174,7 @@ class ImageFileService {
|
|||
@required String uri,
|
||||
@required String mimeType,
|
||||
@required int rotationDegrees,
|
||||
@required int page,
|
||||
@required int pageId,
|
||||
@required bool isFlipped,
|
||||
@required int dateModifiedSecs,
|
||||
@required double extent,
|
||||
|
@ -195,7 +195,7 @@ class ImageFileService {
|
|||
'isFlipped': isFlipped,
|
||||
'widthDip': extent,
|
||||
'heightDip': extent,
|
||||
'page': page,
|
||||
'pageId': pageId,
|
||||
'defaultSizeDip': thumbnailDefaultSize,
|
||||
});
|
||||
return result as Uint8List;
|
||||
|
|
|
@ -87,8 +87,9 @@ class MetadataService {
|
|||
final result = await platform.invokeMethod('getMultiPageInfo', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
}) as Map;
|
||||
return MultiPageInfo.fromMap(result);
|
||||
});
|
||||
final pageMaps = (result as List).cast<Map>();
|
||||
return MultiPageInfo.fromPageMaps(pageMaps);
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('getMultiPageInfo failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import 'package:flutter/material.dart';
|
|||
class RasterImageThumbnail extends StatefulWidget {
|
||||
final AvesEntry entry;
|
||||
final double extent;
|
||||
final int page;
|
||||
final ValueNotifier<bool> isScrollingNotifier;
|
||||
final Object heroTag;
|
||||
|
||||
|
@ -17,7 +16,6 @@ class RasterImageThumbnail extends StatefulWidget {
|
|||
Key key,
|
||||
@required this.entry,
|
||||
@required this.extent,
|
||||
this.page,
|
||||
this.isScrollingNotifier,
|
||||
this.heroTag,
|
||||
}) : super(key: key);
|
||||
|
|
|
@ -62,7 +62,7 @@ class _MultiEntryScrollerState extends State<MultiEntryScroller> with AutomaticK
|
|||
return ValueListenableBuilder<int>(
|
||||
valueListenable: multiPageController.pageNotifier,
|
||||
builder: (context, page, child) {
|
||||
return _buildViewer(entry, multiPageInfo: multiPageInfo, page: page);
|
||||
return _buildViewer(entry, page: multiPageInfo?.getByIndex(page));
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -80,14 +80,13 @@ class _MultiEntryScrollerState extends State<MultiEntryScroller> with AutomaticK
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildViewer(AvesEntry entry, {MultiPageInfo multiPageInfo, int page}) {
|
||||
Widget _buildViewer(AvesEntry entry, {SinglePageInfo page}) {
|
||||
return Selector<MediaQueryData, Size>(
|
||||
selector: (c, mq) => mq.size,
|
||||
builder: (c, mqSize, child) {
|
||||
return EntryPageView(
|
||||
key: Key('imageview'),
|
||||
mainEntry: entry,
|
||||
multiPageInfo: multiPageInfo,
|
||||
page: page,
|
||||
viewportSize: mqSize,
|
||||
heroTag: widget.collection.heroTag(entry),
|
||||
|
@ -142,7 +141,7 @@ class _SingleEntryScrollerState extends State<SingleEntryScroller> with Automati
|
|||
return ValueListenableBuilder<int>(
|
||||
valueListenable: multiPageController.pageNotifier,
|
||||
builder: (context, page, child) {
|
||||
return _buildViewer(multiPageInfo: multiPageInfo, page: page);
|
||||
return _buildViewer(page: multiPageInfo?.getByIndex(page));
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -157,13 +156,12 @@ class _SingleEntryScrollerState extends State<SingleEntryScroller> with Automati
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildViewer({MultiPageInfo multiPageInfo, int page}) {
|
||||
Widget _buildViewer({SinglePageInfo page}) {
|
||||
return Selector<MediaQueryData, Size>(
|
||||
selector: (c, mq) => mq.size,
|
||||
builder: (c, mqSize, child) {
|
||||
return EntryPageView(
|
||||
mainEntry: entry,
|
||||
multiPageInfo: multiPageInfo,
|
||||
page: page,
|
||||
viewportSize: mqSize,
|
||||
onTap: (_) => widget.onTap?.call(),
|
||||
|
|
|
@ -7,10 +7,16 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class MultiPageController extends ChangeNotifier {
|
||||
final Future<MultiPageInfo> info;
|
||||
final ValueNotifier<int> pageNotifier = ValueNotifier(0);
|
||||
Future<MultiPageInfo> info;
|
||||
final ValueNotifier<int> pageNotifier = ValueNotifier(null);
|
||||
|
||||
MultiPageController(AvesEntry entry) : info = MetadataService.getMultiPageInfo(entry);
|
||||
MultiPageController(AvesEntry entry) {
|
||||
info = MetadataService.getMultiPageInfo(entry).then((value) {
|
||||
final defaultPage = value.pages.firstWhere((page) => page.isDefault, orElse: () => null);
|
||||
pageNotifier.value = defaultPage?.index ?? 0;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
int get page => pageNotifier.value;
|
||||
|
||||
|
|
|
@ -101,8 +101,7 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
|
|||
|
||||
Widget _buildContent({MultiPageInfo multiPageInfo, int page}) => _BottomOverlayContent(
|
||||
mainEntry: _lastEntry,
|
||||
multiPageInfo: multiPageInfo,
|
||||
page: page,
|
||||
page: multiPageInfo?.getByIndex(page),
|
||||
details: _lastDetails,
|
||||
position: widget.showPosition ? '${widget.index + 1}/${widget.entries.length}' : null,
|
||||
availableWidth: availableWidth,
|
||||
|
@ -139,8 +138,7 @@ const double _subRowMinWidth = 300.0;
|
|||
|
||||
class _BottomOverlayContent extends AnimatedWidget {
|
||||
final AvesEntry mainEntry, entry;
|
||||
final MultiPageInfo multiPageInfo;
|
||||
final int page;
|
||||
final SinglePageInfo page;
|
||||
final OverlayMetadata details;
|
||||
final String position;
|
||||
final double availableWidth;
|
||||
|
@ -151,13 +149,12 @@ class _BottomOverlayContent extends AnimatedWidget {
|
|||
_BottomOverlayContent({
|
||||
Key key,
|
||||
this.mainEntry,
|
||||
this.multiPageInfo,
|
||||
this.page,
|
||||
this.details,
|
||||
this.position,
|
||||
this.availableWidth,
|
||||
this.multiPageController,
|
||||
}) : entry = mainEntry.getPageEntry(multiPageInfo: multiPageInfo, page: page),
|
||||
}) : entry = mainEntry.getPageEntry(page),
|
||||
super(key: key, listenable: mainEntry.metadataChangeNotifier);
|
||||
|
||||
@override
|
||||
|
@ -342,6 +339,8 @@ class _PositionTitleRow extends StatelessWidget {
|
|||
|
||||
bool get isNotEmpty => collectionPosition != null || multiPageController != null || title != null;
|
||||
|
||||
static const separator = ' • ';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Text toText({String pagePosition}) => Text(
|
||||
|
@ -349,7 +348,7 @@ class _PositionTitleRow extends StatelessWidget {
|
|||
if (collectionPosition != null) collectionPosition,
|
||||
if (pagePosition != null) pagePosition,
|
||||
if (title != null) title,
|
||||
].join(' • '),
|
||||
].join(separator),
|
||||
strutStyle: Constants.overflowStrutStyle);
|
||||
|
||||
if (multiPageController == null) return toText();
|
||||
|
@ -358,11 +357,17 @@ class _PositionTitleRow extends StatelessWidget {
|
|||
future: multiPageController.info,
|
||||
builder: (context, snapshot) {
|
||||
final multiPageInfo = snapshot.data;
|
||||
final pageCount = multiPageInfo?.pageCount;
|
||||
// page count may be 0 when we know an entry to have multiple pages
|
||||
// but fail to get information about these pages
|
||||
final missingInfo = pageCount == 0;
|
||||
return toText(pagePosition: missingInfo ? null : '${(entry.page ?? 0) + 1}/${pageCount ?? '?'}');
|
||||
String pagePosition;
|
||||
if (multiPageInfo != null) {
|
||||
// page count may be 0 when we know an entry to have multiple pages
|
||||
// but fail to get information about these pages
|
||||
final pageCount = multiPageInfo.pageCount;
|
||||
if (pageCount > 0) {
|
||||
final page = multiPageInfo.getById(entry.pageId);
|
||||
pagePosition = '${(page?.index ?? 0) + 1}/$pageCount';
|
||||
}
|
||||
}
|
||||
return toText(pagePosition: pagePosition);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class Minimap extends StatelessWidget {
|
|||
return ValueListenableBuilder<int>(
|
||||
valueListenable: multiPageController.pageNotifier,
|
||||
builder: (context, page, child) {
|
||||
final pageEntry = mainEntry.getPageEntry(multiPageInfo: multiPageInfo, page: page);
|
||||
final pageEntry = mainEntry.getPageEntry(multiPageInfo?.getByIndex(page));
|
||||
return _buildForEntrySize(pageEntry.displaySize);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -62,7 +62,8 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
|
|||
}
|
||||
|
||||
void _registerWidget() {
|
||||
final scrollOffset = pageToScrollOffset(controller.page);
|
||||
final page = controller.page ?? 0;
|
||||
final scrollOffset = pageToScrollOffset(page);
|
||||
_scrollController = ScrollController(initialScrollOffset: scrollOffset);
|
||||
_scrollController.addListener(_onScrollChange);
|
||||
}
|
||||
|
@ -108,7 +109,7 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
|
|||
itemBuilder: (context, index) {
|
||||
if (index == 0 || index == multiPageInfo.pageCount + 1) return horizontalMargin;
|
||||
final page = index - 1;
|
||||
final pageEntry = mainEntry.getPageEntry(multiPageInfo: multiPageInfo, page: page);
|
||||
final pageEntry = mainEntry.getPageEntry(multiPageInfo.getByIndex(page));
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
|
|
|
@ -46,8 +46,8 @@ class EntryPrinter {
|
|||
if (entry.isMultipage) {
|
||||
final multiPageInfo = await MetadataService.getMultiPageInfo(entry);
|
||||
if (multiPageInfo.pageCount > 1) {
|
||||
for (final kv in multiPageInfo.pages.entries) {
|
||||
final pageEntry = entry.getPageEntry(multiPageInfo: multiPageInfo, page: kv.key);
|
||||
for (final page in multiPageInfo.pages) {
|
||||
final pageEntry = entry.getPageEntry(page);
|
||||
_addPdfPage(await _buildPageImage(pageEntry));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,7 @@ import 'package:tuple/tuple.dart';
|
|||
|
||||
class EntryPageView extends StatefulWidget {
|
||||
final AvesEntry entry;
|
||||
final MultiPageInfo multiPageInfo;
|
||||
final int page;
|
||||
final SinglePageInfo page;
|
||||
final Size viewportSize;
|
||||
final Object heroTag;
|
||||
final MagnifierTapCallback onTap;
|
||||
|
@ -37,14 +36,13 @@ class EntryPageView extends StatefulWidget {
|
|||
EntryPageView({
|
||||
Key key,
|
||||
AvesEntry mainEntry,
|
||||
this.multiPageInfo,
|
||||
this.page,
|
||||
this.viewportSize,
|
||||
this.heroTag,
|
||||
@required this.onTap,
|
||||
@required this.videoControllers,
|
||||
this.onDisposed,
|
||||
}) : entry = mainEntry.getPageEntry(multiPageInfo: multiPageInfo, page: page) ?? mainEntry,
|
||||
}) : entry = mainEntry.getPageEntry(page) ?? mainEntry,
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -198,7 +196,7 @@ class _EntryPageViewState extends State<EntryPageView> {
|
|||
}) {
|
||||
return Magnifier(
|
||||
// key includes size and orientation to refresh when the image is rotated
|
||||
key: ValueKey('${entry.page}_${entry.rotationDegrees}_${entry.isFlipped}_${entry.width}_${entry.height}_${entry.path}'),
|
||||
key: ValueKey('${entry.pageId}_${entry.rotationDegrees}_${entry.isFlipped}_${entry.width}_${entry.height}_${entry.path}'),
|
||||
controller: _magnifierController,
|
||||
childSize: entry.displaySize,
|
||||
minScale: minScale,
|
||||
|
|
Loading…
Reference in a new issue