Merge branch 'main' into uncategorized
This commit is contained in:
commit
98a22c34a5
14 changed files with 219 additions and 88 deletions
|
@ -477,6 +477,7 @@ class RemoteSyncService {
|
|||
final int toBeUploaded = filesToBeUploaded.length + updatedFileIDs.length;
|
||||
if (toBeUploaded > 0) {
|
||||
Bus.instance.fire(SyncStatusUpdate(SyncStatus.preparingForUpload));
|
||||
await _uploader.checkNetworkForUpload();
|
||||
// verify if files upload is allowed based on their subscription plan and
|
||||
// storage limit. To avoid creating new endpoint, we are using
|
||||
// fetchUploadUrls as alternative method.
|
||||
|
|
|
@ -21,6 +21,7 @@ class EnteColorScheme {
|
|||
|
||||
// Fill Colors
|
||||
final Color fillBase;
|
||||
final Color fillBasePressed;
|
||||
final Color fillMuted;
|
||||
final Color fillFaint;
|
||||
final Color fillFaintPressed;
|
||||
|
@ -44,6 +45,7 @@ class EnteColorScheme {
|
|||
final Color warning700;
|
||||
final Color warning500;
|
||||
final Color warning400;
|
||||
final Color warning800;
|
||||
final Color caution500;
|
||||
|
||||
//other colors
|
||||
|
@ -62,6 +64,7 @@ class EnteColorScheme {
|
|||
this.textFaint,
|
||||
this.blurTextBase,
|
||||
this.fillBase,
|
||||
this.fillBasePressed,
|
||||
this.fillMuted,
|
||||
this.fillFaint,
|
||||
this.fillFaintPressed,
|
||||
|
@ -78,9 +81,10 @@ class EnteColorScheme {
|
|||
this.primary500 = _primary500,
|
||||
this.primary400 = _primary400,
|
||||
this.primary300 = _primary300,
|
||||
this.warning800 = _warning800,
|
||||
this.warning700 = _warning700,
|
||||
this.warning500 = _warning500,
|
||||
this.warning400 = _warning700,
|
||||
this.warning400 = _warning400,
|
||||
this.caution500 = _caution500,
|
||||
});
|
||||
}
|
||||
|
@ -97,6 +101,7 @@ const EnteColorScheme lightScheme = EnteColorScheme(
|
|||
textFaintLight,
|
||||
blurTextBaseLight,
|
||||
fillBaseLight,
|
||||
fillBasePressedLight,
|
||||
fillMutedLight,
|
||||
fillFaintLight,
|
||||
fillFaintPressedLight,
|
||||
|
@ -123,6 +128,7 @@ const EnteColorScheme darkScheme = EnteColorScheme(
|
|||
textFaintDark,
|
||||
blurTextBaseDark,
|
||||
fillBaseDark,
|
||||
fillBasePressedDark,
|
||||
fillMutedDark,
|
||||
fillFaintDark,
|
||||
fillFaintPressedDark,
|
||||
|
@ -168,11 +174,13 @@ const Color blurTextBaseDark = Color.fromRGBO(255, 255, 255, 0.95);
|
|||
|
||||
// Fill Colors
|
||||
const Color fillBaseLight = Color.fromRGBO(0, 0, 0, 1);
|
||||
const Color fillBasePressedLight = Color.fromRGBO(0, 0, 0, 0.87);
|
||||
const Color fillMutedLight = Color.fromRGBO(0, 0, 0, 0.12);
|
||||
const Color fillFaintLight = Color.fromRGBO(0, 0, 0, 0.04);
|
||||
const Color fillFaintPressedLight = Color.fromRGBO(0, 0, 0, 0.08);
|
||||
|
||||
const Color fillBaseDark = Color.fromRGBO(255, 255, 255, 1);
|
||||
const Color fillBasePressedDark = Color.fromRGBO(255, 255, 255, 0.9);
|
||||
const Color fillMutedDark = Color.fromRGBO(255, 255, 255, 0.16);
|
||||
const Color fillFaintDark = Color.fromRGBO(255, 255, 255, 0.12);
|
||||
const Color fillFaintPressedDark = Color.fromRGBO(255, 255, 255, 0.06);
|
||||
|
@ -212,6 +220,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1);
|
|||
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
|
||||
const Color _warning800 = Color(0xFFF53434);
|
||||
|
||||
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
|
||||
|
||||
|
|
|
@ -4,18 +4,22 @@ import 'package:photos/models/collection.dart';
|
|||
import 'package:photos/models/collection_items.dart';
|
||||
import 'package:photos/models/gallery_type.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/viewer/file/file_icons_widget.dart';
|
||||
import 'package:photos/ui/viewer/file/no_thumbnail_widget.dart';
|
||||
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
|
||||
import 'package:photos/ui/viewer/gallery/collection_page.dart';
|
||||
import 'package:photos/utils/navigation_util.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
class CollectionItem extends StatelessWidget {
|
||||
final CollectionWithThumbnail c;
|
||||
final double sideOfThumbnail;
|
||||
final bool shouldRender;
|
||||
|
||||
CollectionItem(
|
||||
this.c,
|
||||
this.sideOfThumbnail, {
|
||||
this.shouldRender = false,
|
||||
Key? key,
|
||||
}) : super(key: Key(c.collection.id.toString()));
|
||||
|
||||
|
@ -39,11 +43,10 @@ class CollectionItem extends StatelessWidget {
|
|||
child: Hero(
|
||||
tag: heroTag,
|
||||
child: c.thumbnail != null
|
||||
? ThumbnailWidget(
|
||||
c.thumbnail,
|
||||
shouldShowArchiveStatus: c.collection.isArchived(),
|
||||
showFavForAlbumOnly: true,
|
||||
key: Key(heroTag),
|
||||
? CollectionItemThumbnailWidget(
|
||||
c: c,
|
||||
heroTag: heroTag,
|
||||
shouldRender: shouldRender,
|
||||
)
|
||||
: const NoThumbnailWidget(),
|
||||
),
|
||||
|
@ -98,3 +101,54 @@ class CollectionItem extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CollectionItemThumbnailWidget extends StatefulWidget {
|
||||
const CollectionItemThumbnailWidget({
|
||||
Key? key,
|
||||
required this.c,
|
||||
required this.heroTag,
|
||||
this.shouldRender = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final CollectionWithThumbnail c;
|
||||
final String heroTag;
|
||||
final bool shouldRender;
|
||||
|
||||
@override
|
||||
State<CollectionItemThumbnailWidget> createState() =>
|
||||
_CollectionItemThumbnailWidgetState();
|
||||
}
|
||||
|
||||
class _CollectionItemThumbnailWidgetState
|
||||
extends State<CollectionItemThumbnailWidget> {
|
||||
bool _shouldRender = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_shouldRender = widget.shouldRender;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return VisibilityDetector(
|
||||
key: Key("collection_item" + widget.c.thumbnail!.tag),
|
||||
onVisibilityChanged: (visibility) {
|
||||
final shouldRender = visibility.visibleFraction > 0;
|
||||
if (mounted && shouldRender && !_shouldRender) {
|
||||
setState(() {
|
||||
_shouldRender = shouldRender;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: _shouldRender
|
||||
? ThumbnailWidget(
|
||||
widget.c.thumbnail,
|
||||
shouldShowArchiveStatus: widget.c.collection.isArchived(),
|
||||
showFavForAlbumOnly: true,
|
||||
key: Key(widget.heroTag),
|
||||
)
|
||||
: const ThumbnailPlaceHolder(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ class RemoteCollectionsGridViewWidget extends StatelessWidget {
|
|||
static const maxThumbnailWidth = 224.0;
|
||||
static const fixedGapBetweenAlbum = 8.0;
|
||||
static const minGapForHorizontalPadding = 8.0;
|
||||
static const collectionItemsToPreload = 20;
|
||||
|
||||
final List<CollectionWithThumbnail>? collections;
|
||||
|
||||
|
@ -45,7 +46,11 @@ class RemoteCollectionsGridViewWidget extends StatelessWidget {
|
|||
// to disable GridView's scrolling
|
||||
itemBuilder: (context, index) {
|
||||
if (index < collections!.length) {
|
||||
return CollectionItem(collections![index], sideOfThumbnail);
|
||||
return CollectionItem(
|
||||
collections![index],
|
||||
sideOfThumbnail,
|
||||
shouldRender: index < collectionItemsToPreload,
|
||||
);
|
||||
} else {
|
||||
return const CreateNewAlbumWidget();
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ class ContentContainerWidget extends StatelessWidget {
|
|||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
title!,
|
||||
style: textTheme.h3Bold
|
||||
style: textTheme.largeBold
|
||||
.copyWith(color: textBaseDark), //constant color
|
||||
),
|
||||
title == null || body == null
|
||||
|
|
|
@ -115,7 +115,6 @@ class ButtonWidget extends StatelessWidget {
|
|||
buttonType.defaultBorderColor(colorScheme, buttonSize);
|
||||
buttonStyle.pressedBorderColor = buttonType.pressedBorderColor(
|
||||
colorScheme: colorScheme,
|
||||
inverseColorScheme: inverseColorScheme,
|
||||
buttonSize: buttonSize,
|
||||
);
|
||||
buttonStyle.disabledBorderColor =
|
||||
|
@ -205,27 +204,20 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
|
|||
final _debouncer = Debouncer(const Duration(milliseconds: 300));
|
||||
ExecutionState executionState = ExecutionState.idle;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_setButtonTheme();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ButtonChildWidget oldWidget) {
|
||||
_setButtonTheme();
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
progressStatus = widget.progressStatus;
|
||||
checkIconColor = widget.buttonStyle.checkIconColor ??
|
||||
widget.buttonStyle.defaultIconColor;
|
||||
loadingIconColor = widget.buttonStyle.defaultIconColor;
|
||||
if (widget.isDisabled) {
|
||||
buttonColor = widget.buttonStyle.disabledButtonColor ??
|
||||
widget.buttonStyle.defaultButtonColor;
|
||||
borderColor = widget.buttonStyle.disabledBorderColor ??
|
||||
widget.buttonStyle.defaultBorderColor;
|
||||
iconColor = widget.buttonStyle.disabledIconColor ??
|
||||
widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.disabledLabelStyle ??
|
||||
widget.buttonStyle.defaultLabelStyle;
|
||||
} else {
|
||||
buttonColor = widget.buttonStyle.defaultButtonColor;
|
||||
borderColor = widget.buttonStyle.defaultBorderColor;
|
||||
iconColor = widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.defaultLabelStyle;
|
||||
}
|
||||
if (executionState == ExecutionState.successful) {
|
||||
Future.delayed(Duration(seconds: widget.isInAlert ? 1 : 2), () {
|
||||
setState(() {
|
||||
|
@ -241,7 +233,9 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
|
|||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
border: Border.all(color: borderColor),
|
||||
border: widget.buttonType == ButtonType.tertiaryCritical
|
||||
? Border.all(color: borderColor)
|
||||
: null,
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 16),
|
||||
|
@ -383,6 +377,28 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
void _setButtonTheme() {
|
||||
progressStatus = widget.progressStatus;
|
||||
checkIconColor = widget.buttonStyle.checkIconColor ??
|
||||
widget.buttonStyle.defaultIconColor;
|
||||
loadingIconColor = widget.buttonStyle.defaultIconColor;
|
||||
if (widget.isDisabled) {
|
||||
buttonColor = widget.buttonStyle.disabledButtonColor ??
|
||||
widget.buttonStyle.defaultButtonColor;
|
||||
borderColor = widget.buttonStyle.disabledBorderColor ??
|
||||
widget.buttonStyle.defaultBorderColor;
|
||||
iconColor = widget.buttonStyle.disabledIconColor ??
|
||||
widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.disabledLabelStyle ??
|
||||
widget.buttonStyle.defaultLabelStyle;
|
||||
} else {
|
||||
buttonColor = widget.buttonStyle.defaultButtonColor;
|
||||
borderColor = widget.buttonStyle.defaultBorderColor;
|
||||
iconColor = widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.defaultLabelStyle;
|
||||
}
|
||||
}
|
||||
|
||||
bool get _shouldRegisterGestures =>
|
||||
!widget.isDisabled && executionState == ExecutionState.idle;
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ class ContentContainer extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
icon == null ? const SizedBox.shrink() : const SizedBox(height: 19),
|
||||
Text(title, style: textTheme.h3Bold),
|
||||
Text(title, style: textTheme.largeBold),
|
||||
body != null ? const SizedBox(height: 19) : const SizedBox.shrink(),
|
||||
body != null
|
||||
? Text(
|
||||
|
|
|
@ -55,6 +55,15 @@ enum ButtonType {
|
|||
if (isPrimary) {
|
||||
return colorScheme.primary700;
|
||||
}
|
||||
if (isSecondary) {
|
||||
return colorScheme.fillFaintPressed;
|
||||
}
|
||||
if (isNeutral) {
|
||||
return colorScheme.fillBasePressed;
|
||||
}
|
||||
if (this == ButtonType.critical) {
|
||||
return colorScheme.warning800;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -85,20 +94,10 @@ enum ButtonType {
|
|||
//Returning null to fallback to default color
|
||||
Color? pressedBorderColor({
|
||||
required EnteColorScheme colorScheme,
|
||||
required EnteColorScheme inverseColorScheme,
|
||||
required ButtonSize buttonSize,
|
||||
}) {
|
||||
if (isPrimary) {
|
||||
return colorScheme.strokeMuted;
|
||||
}
|
||||
if (buttonSize == ButtonSize.small && this == ButtonType.tertiaryCritical) {
|
||||
return null;
|
||||
}
|
||||
if (isSecondary || isCritical) {
|
||||
return colorScheme.strokeBase;
|
||||
}
|
||||
if (isNeutral) {
|
||||
return inverseColorScheme.strokeBase;
|
||||
if (this == ButtonType.tertiaryCritical && buttonSize == ButtonSize.large) {
|
||||
return colorScheme.warning700;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -133,8 +132,11 @@ enum ButtonType {
|
|||
|
||||
//Returning null to fallback to default color
|
||||
Color? pressedIconColor(EnteColorScheme colorScheme, ButtonSize buttonSize) {
|
||||
if (this == ButtonType.tertiaryCritical && buttonSize == ButtonSize.large) {
|
||||
return colorScheme.strokeBase;
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return colorScheme.warning700;
|
||||
}
|
||||
if (this == ButtonType.tertiary && buttonSize == ButtonSize.small) {
|
||||
return colorScheme.fillBasePressed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -176,8 +178,11 @@ enum ButtonType {
|
|||
EnteColorScheme colorScheme,
|
||||
ButtonSize buttonSize,
|
||||
) {
|
||||
if (this == ButtonType.tertiaryCritical && buttonSize == ButtonSize.large) {
|
||||
return textTheme.bodyBold.copyWith(color: colorScheme.strokeBase);
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return textTheme.bodyBold.copyWith(color: colorScheme.warning700);
|
||||
}
|
||||
if (this == ButtonType.tertiary && buttonSize == ButtonSize.small) {
|
||||
return textTheme.bodyBold.copyWith(color: colorScheme.fillBasePressed);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -240,34 +240,6 @@ class RefreshIndicatorWidget extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class BrandingWidget extends StatelessWidget {
|
||||
const BrandingWidget({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
height: kContainerHeight,
|
||||
padding: const EdgeInsets.only(left: 12, top: 4),
|
||||
child: const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
"ente",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Montserrat',
|
||||
fontSize: 24,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SyncStatusCompletedWidget extends StatelessWidget {
|
||||
const SyncStatusCompletedWidget({Key? key}) : super(key: key);
|
||||
|
||||
|
|
|
@ -352,7 +352,7 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
|
||||
Widget _getRecyclableView() {
|
||||
return VisibilityDetector(
|
||||
key: UniqueKey(),
|
||||
key: Key("gallery" + widget.filesInDay.first.tag),
|
||||
onVisibilityChanged: (visibility) {
|
||||
final shouldRender = visibility.visibleFraction > 0;
|
||||
if (mounted && shouldRender != _shouldRender) {
|
||||
|
@ -370,7 +370,7 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
Widget _getNonRecyclableView() {
|
||||
if (!_shouldRender!) {
|
||||
return VisibilityDetector(
|
||||
key: UniqueKey(),
|
||||
key: Key("gallery" + widget.filesInDay.first.tag),
|
||||
onVisibilityChanged: (visibility) {
|
||||
if (mounted && visibility.visibleFraction > 0 && !_shouldRender!) {
|
||||
setState(() {
|
||||
|
|
|
@ -23,7 +23,10 @@ class UnSyncedIcon extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const _BottomLeftOverlayIcon(Icons.cloud_off_outlined);
|
||||
return const _BottomLeftOverlayIcon(
|
||||
Icons.cloud_off_outlined,
|
||||
baseSize: 18,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +189,7 @@ class _BottomLeftOverlayIcon extends StatelessWidget {
|
|||
|
||||
if (constraints.hasBoundedWidth) {
|
||||
final w = constraints.maxWidth;
|
||||
if (w > 120) {
|
||||
if (w > 125) {
|
||||
size = 24;
|
||||
} else if (w < 75) {
|
||||
inset = 3;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -13,6 +14,7 @@ import 'package:photos/events/local_photos_updated_event.dart';
|
|||
import 'package:photos/models/file.dart';
|
||||
import 'package:photos/ui/common/loading_widget.dart';
|
||||
import 'package:photos/utils/file_util.dart';
|
||||
import 'package:photos/utils/image_util.dart';
|
||||
import 'package:photos/utils/thumbnail_util.dart';
|
||||
|
||||
class ZoomableImage extends StatefulWidget {
|
||||
|
@ -35,7 +37,7 @@ class ZoomableImage extends StatefulWidget {
|
|||
|
||||
class _ZoomableImageState extends State<ZoomableImage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final Logger _logger = Logger("ZoomableImage");
|
||||
late Logger _logger;
|
||||
late File _photo;
|
||||
ImageProvider? _imageProvider;
|
||||
bool _loadedSmallThumbnail = false;
|
||||
|
@ -45,10 +47,13 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|||
bool _loadedFinalImage = false;
|
||||
ValueChanged<PhotoViewScaleState>? _scaleStateChangedCallback;
|
||||
bool _isZooming = false;
|
||||
PhotoViewController _photoViewController = PhotoViewController();
|
||||
int? _thumbnailWidth;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_photo = widget.photo;
|
||||
_logger = Logger("ZoomableImage_" + _photo.displayName);
|
||||
debugPrint('initState for ${_photo.toString()}');
|
||||
_scaleStateChangedCallback = (value) {
|
||||
if (widget.shouldDisableScroll != null) {
|
||||
|
@ -61,6 +66,12 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_photoViewController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_photo.isRemoteFile) {
|
||||
|
@ -75,6 +86,7 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|||
axis: Axis.vertical,
|
||||
child: PhotoView(
|
||||
imageProvider: _imageProvider,
|
||||
controller: _photoViewController,
|
||||
scaleStateChangedCallback: _scaleStateChangedCallback,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
gaplessPlayback: true,
|
||||
|
@ -106,6 +118,7 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|||
if (cachedThumbnail != null) {
|
||||
_imageProvider = Image.memory(cachedThumbnail).image;
|
||||
_loadedSmallThumbnail = true;
|
||||
_captureThumbnailDimensions(_imageProvider!);
|
||||
} else {
|
||||
getThumbnailFromServer(_photo).then((file) {
|
||||
final imageProvider = Image.memory(file).image;
|
||||
|
@ -115,6 +128,7 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|||
setState(() {
|
||||
_imageProvider = imageProvider;
|
||||
_loadedSmallThumbnail = true;
|
||||
_captureThumbnailDimensions(_imageProvider!);
|
||||
});
|
||||
}
|
||||
}).catchError((e) {
|
||||
|
@ -125,7 +139,8 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|||
});
|
||||
}
|
||||
}
|
||||
if (!_loadedFinalImage) {
|
||||
if (!_loadedFinalImage && !_loadingFinalImage) {
|
||||
_loadingFinalImage = true;
|
||||
getFileFromServer(_photo).then((file) {
|
||||
_onFinalImageLoaded(
|
||||
Image.file(
|
||||
|
@ -209,16 +224,42 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|||
|
||||
void _onFinalImageLoaded(ImageProvider imageProvider) {
|
||||
if (mounted) {
|
||||
precacheImage(imageProvider, context).then((value) {
|
||||
precacheImage(imageProvider, context).then((value) async {
|
||||
if (mounted) {
|
||||
await _updatePhotoViewController(imageProvider);
|
||||
setState(() {
|
||||
_imageProvider = imageProvider;
|
||||
_loadedFinalImage = true;
|
||||
_logger.info("Final image loaded");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _captureThumbnailDimensions(ImageProvider imageProvider) async {
|
||||
final imageInfo = await getImageInfo(imageProvider);
|
||||
_thumbnailWidth = imageInfo.image.width;
|
||||
}
|
||||
|
||||
Future<void> _updatePhotoViewController(ImageProvider imageProvider) async {
|
||||
if (_thumbnailWidth == null || _photoViewController.scale == null) {
|
||||
return;
|
||||
}
|
||||
final imageInfo = await getImageInfo(imageProvider);
|
||||
final scale = _photoViewController.scale! /
|
||||
(imageInfo.image.width / _thumbnailWidth!);
|
||||
final currentPosition = _photoViewController.value.position;
|
||||
final positionScaleFactor = 1 / scale;
|
||||
final newPosition = currentPosition.scale(
|
||||
positionScaleFactor,
|
||||
positionScaleFactor,
|
||||
);
|
||||
_photoViewController = PhotoViewController(
|
||||
initialPosition: newPosition,
|
||||
initialScale: scale,
|
||||
);
|
||||
}
|
||||
|
||||
bool _isGIF() => _photo.displayName.toLowerCase().endsWith(".gif");
|
||||
}
|
||||
|
|
|
@ -271,18 +271,27 @@ class FileUploader {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> checkNetworkForUpload({bool isForceUpload = false}) async {
|
||||
// Note: We don't support force uploading currently. During force upload,
|
||||
// network check is skipped completely
|
||||
if (isForceUpload) {
|
||||
return;
|
||||
}
|
||||
final connectivityResult = await (Connectivity().checkConnectivity());
|
||||
final canUploadUnderCurrentNetworkConditions =
|
||||
(connectivityResult == ConnectivityResult.wifi ||
|
||||
Configuration.instance.shouldBackupOverMobileData());
|
||||
if (!canUploadUnderCurrentNetworkConditions) {
|
||||
throw WiFiUnavailableError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<File> _tryToUpload(
|
||||
File file,
|
||||
int collectionID,
|
||||
bool forcedUpload,
|
||||
) async {
|
||||
final connectivityResult = await (Connectivity().checkConnectivity());
|
||||
final canUploadUnderCurrentNetworkConditions =
|
||||
(connectivityResult == ConnectivityResult.wifi ||
|
||||
Configuration.instance.shouldBackupOverMobileData());
|
||||
if (!canUploadUnderCurrentNetworkConditions && !forcedUpload) {
|
||||
throw WiFiUnavailableError();
|
||||
}
|
||||
await checkNetworkForUpload(isForceUpload: forcedUpload);
|
||||
final fileOnDisk = await FilesDB.instance.getFile(file.generatedID!);
|
||||
final wasAlreadyUploaded = fileOnDisk != null &&
|
||||
fileOnDisk.uploadedFileID != null &&
|
||||
|
|
16
lib/utils/image_util.dart
Normal file
16
lib/utils/image_util.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
Future<ImageInfo> getImageInfo(ImageProvider imageProvider) {
|
||||
final completer = Completer<ImageInfo>();
|
||||
final imageStream = imageProvider.resolve(const ImageConfiguration());
|
||||
final listener = ImageStreamListener(
|
||||
((imageInfo, _) {
|
||||
completer.complete(imageInfo);
|
||||
}),
|
||||
);
|
||||
imageStream.addListener(listener);
|
||||
completer.future.whenComplete(() => imageStream.removeListener(listener));
|
||||
return completer.future;
|
||||
}
|
Loading…
Add table
Reference in a new issue