This commit is contained in:
Thibault Deckers 2019-11-20 08:26:18 +09:00
parent 993f189377
commit 6203b98ff4
10 changed files with 100 additions and 30 deletions

View file

@ -12,6 +12,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:screen/screen.dart';
void main() { void main() {
// initialize binding/plugins to configure Skia before `runApp` // initialize binding/plugins to configure Skia before `runApp`
@ -60,6 +61,7 @@ class _HomePageState extends State<HomePage> {
super.initState(); super.initState();
imageCache.maximumSizeBytes = 100 * 1024 * 1024; imageCache.maximumSizeBytes = 100 * 1024 * 1024;
setup(); setup();
Screen.keepOn(true);
} }
setup() async { setup() async {

View file

@ -17,6 +17,7 @@ class ImageFileService {
static Future<Uint8List> getImageBytes(ImageEntry entry, int width, int height) async { static Future<Uint8List> getImageBytes(ImageEntry entry, int width, int height) async {
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
// debugPrint('getImageBytes width=$width path=${entry.path}');
try { try {
final result = await platform.invokeMethod('getImageBytes', <String, dynamic>{ final result = await platform.invokeMethod('getImageBytes', <String, dynamic>{
'entry': entry.toMap(), 'entry': entry.toMap(),

View file

@ -1,5 +1,8 @@
import 'dart:ui';
import 'package:aves/model/image_collection.dart'; import 'package:aves/model/image_collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
final Settings settings = Settings._private(); final Settings settings = Settings._private();
@ -19,15 +22,35 @@ class Settings {
static const infoMapZoomKey = 'info_map_zoom'; static const infoMapZoomKey = 'info_map_zoom';
static const catalogTimeZoneKey = 'catalog_time_zone'; static const catalogTimeZoneKey = 'catalog_time_zone';
// state
static const windowMetricsKey = 'window_metrics';
init() async { init() async {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
// TODO TLAD try this as an alternative to MediaQuery, in order to rebuild only on specific property change
// window.onMetricsChanged = onMetricsChanged;
} }
void addListener(SettingsCallback listener) => _listeners.add(listener); WindowMetrics _metrics;
void removeListener(SettingsCallback listener) => _listeners.remove(listener); onMetricsChanged() {
final newValue = WindowMetrics(
devicePixelRatio: window.devicePixelRatio,
physicalSize: window.physicalSize,
viewInsets: window.viewInsets,
viewPadding: window.viewPadding,
systemGestureInsets: window.systemGestureInsets,
padding: window.padding,
);
notifyListeners(windowMetricsKey, _metrics, newValue);
_metrics = newValue;
}
void notifyListeners(String key, dynamic oldValue, dynamic newValue) { addListener(SettingsCallback listener) => _listeners.add(listener);
removeListener(SettingsCallback listener) => _listeners.remove(listener);
notifyListeners(String key, dynamic oldValue, dynamic newValue) {
debugPrint('$runtimeType notifyListeners key=$key, old=$oldValue, new=$newValue'); debugPrint('$runtimeType notifyListeners key=$key, old=$oldValue, new=$newValue');
if (_listeners != null) { if (_listeners != null) {
final List<SettingsCallback> localListeners = _listeners.toList(); final List<SettingsCallback> localListeners = _listeners.toList();
@ -95,3 +118,21 @@ class Settings {
} }
} }
} }
class WindowMetrics {
final double devicePixelRatio;
final Size physicalSize;
final WindowPadding viewInsets;
final WindowPadding viewPadding;
final WindowPadding systemGestureInsets;
final WindowPadding padding;
const WindowMetrics({
this.devicePixelRatio,
this.physicalSize,
this.viewInsets,
this.viewPadding,
this.systemGestureInsets,
this.padding,
});
}

View file

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:aves/model/image_collection.dart'; import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/filtered_collection_page.dart'; import 'package:aves/widgets/album/filtered_collection_page.dart';
@ -16,7 +18,7 @@ class AllCollectionDrawer extends StatelessWidget {
final tags = collection.sortedTags; final tags = collection.sortedTags;
return Drawer( return Drawer(
child: ListView( child: ListView(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), padding: EdgeInsets.only(bottom: window.viewInsets.bottom),
children: [ children: [
DrawerHeader( DrawerHeader(
child: SafeArea( child: SafeArea(

View file

@ -27,7 +27,15 @@ class Thumbnail extends StatelessWidget {
builder: (bytes) { builder: (bytes) {
return Hero( return Hero(
tag: entry.uri, tag: entry.uri,
child: LayoutBuilder(builder: (context, constraints) { flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
// use LayoutBuilder only during hero animation
return LayoutBuilder(builder: (context, constraints) {
final dim = min(constraints.maxWidth, constraints.maxHeight); final dim = min(constraints.maxWidth, constraints.maxHeight);
return Image.memory( return Image.memory(
bytes, bytes,
@ -35,7 +43,14 @@ class Thumbnail extends StatelessWidget {
height: dim, height: dim,
fit: BoxFit.cover, fit: BoxFit.cover,
); );
}), });
},
child: Image.memory(
bytes,
width: extent,
height: extent,
fit: BoxFit.cover,
),
); );
}, },
); );

View file

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:aves/model/image_collection.dart'; import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/sections.dart'; import 'package:aves/widgets/album/sections.dart';
@ -23,6 +25,7 @@ class ThumbnailCollection extends AnimatedWidget {
return ThumbnailCollectionContent( return ThumbnailCollectionContent(
collection: collection, collection: collection,
appBar: appBar, appBar: appBar,
screenWidth: MediaQuery.of(context).size.width,
); );
} }
} }
@ -30,14 +33,16 @@ class ThumbnailCollection extends AnimatedWidget {
class ThumbnailCollectionContent extends StatelessWidget { class ThumbnailCollectionContent extends StatelessWidget {
final ImageCollection collection; final ImageCollection collection;
final Widget appBar; final Widget appBar;
final double screenWidth;
final Map<dynamic, List<ImageEntry>> _sections; final Map<dynamic, List<ImageEntry>> _sections;
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
ThumbnailCollectionContent({ ThumbnailCollectionContent({
Key key, Key key,
this.collection, @required this.collection,
this.appBar, @required this.appBar,
@required this.screenWidth,
}) : _sections = collection.sections, }) : _sections = collection.sections,
super(key: key); super(key: key);
@ -69,6 +74,7 @@ class ThumbnailCollectionContent extends StatelessWidget {
collection: collection, collection: collection,
sections: _sections, sections: _sections,
sectionKey: sectionKey, sectionKey: sectionKey,
screenWidth: screenWidth,
); );
if (sectionKey == sectionKeys.last) { if (sectionKey == sectionKeys.last) {
sliver = SliverPadding( sliver = SliverPadding(
@ -94,12 +100,14 @@ class SectionSliver extends StatelessWidget {
final ImageCollection collection; final ImageCollection collection;
final Map<dynamic, List<ImageEntry>> sections; final Map<dynamic, List<ImageEntry>> sections;
final dynamic sectionKey; final dynamic sectionKey;
final double screenWidth;
const SectionSliver({ const SectionSliver({
Key key, Key key,
@required this.collection, @required this.collection,
@required this.sections, @required this.sections,
@required this.sectionKey, @required this.sectionKey,
@required this.screenWidth,
}) : super(key: key); }) : super(key: key);
@override @override
@ -113,28 +121,29 @@ class SectionSliver extends StatelessWidget {
), ),
sliver: SliverGrid( sliver: SliverGrid(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
// TODO TLAD find out why thumbnails are rebuilt when config change (show/hide status bar)
(sliverContext, index) { (sliverContext, index) {
final sectionEntries = sections[sectionKey]; final sectionEntries = sections[sectionKey];
if (index >= sectionEntries.length) return null; if (index >= sectionEntries.length) return null;
final entry = sectionEntries[index]; final entry = sectionEntries[index];
final mediaQuery = MediaQuery.of(sliverContext);
return GestureDetector( return GestureDetector(
onTap: () => _showFullscreen(sliverContext, entry), onTap: () => _showFullscreen(sliverContext, entry),
child: Thumbnail( child: Thumbnail(
entry: entry, entry: entry,
extent: mediaQuery.size.width / columnCount, extent: screenWidth / columnCount,
devicePixelRatio: mediaQuery.devicePixelRatio, devicePixelRatio: window.devicePixelRatio,
), ),
); );
}, },
childCount: sections[sectionKey].length, childCount: sections[sectionKey].length,
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: true,
), ),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columnCount, crossAxisCount: columnCount,
), ),
), ),
overlapsContent: false,
); );
} }

View file

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/android_app_service.dart'; import 'package:aves/utils/android_app_service.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
@ -104,7 +106,7 @@ class IconUtils {
return AppIcon( return AppIcon(
packageName: packageName, packageName: packageName,
size: IconTheme.of(context).size, size: IconTheme.of(context).size,
devicePixelRatio: MediaQuery.of(context).devicePixelRatio, devicePixelRatio: window.devicePixelRatio,
); );
} }
return null; return null;

View file

@ -10,9 +10,9 @@ import 'package:aves/widgets/fullscreen/overlay/bottom.dart';
import 'package:aves/widgets/fullscreen/overlay/top.dart'; import 'package:aves/widgets/fullscreen/overlay/top.dart';
import 'package:aves/widgets/fullscreen/overlay/video.dart'; import 'package:aves/widgets/fullscreen/overlay/video.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:screen/screen.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
@ -122,15 +122,13 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
showInfo: () => goToVerticalPage(1), showInfo: () => goToVerticalPage(1),
); );
initVideoController(); initVideoController();
Screen.keepOn(true);
initOverlay(); initOverlay();
} }
initOverlay() async { initOverlay() async {
// wait for MaterialPageRoute.transitionDuration // wait for MaterialPageRoute.transitionDuration
// to show overlay after hero animation is complete // to show overlay after hero animation is complete
await Future.delayed(Duration(milliseconds: 300)); await Future.delayed(Duration(milliseconds: (300 * timeDilation).toInt()));
onOverlayVisibleChange(); onOverlayVisibleChange();
} }
@ -150,7 +148,6 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
goToVerticalPage(0); goToVerticalPage(0);
return Future.value(false); return Future.value(false);
} }
Screen.keepOn(false);
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
return Future.value(true); return Future.value(true);
}, },

View file

@ -1,4 +1,5 @@
import 'dart:math'; import 'dart:math';
import 'dart:ui';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/common/image_preview.dart'; import 'package:aves/widgets/common/image_preview.dart';
@ -62,7 +63,7 @@ class AvesVideoState extends State<AvesVideo> {
entry: entry, entry: entry,
width: width, width: width,
height: width / entry.aspectRatio, height: width / entry.aspectRatio,
devicePixelRatio: mediaQuery.devicePixelRatio, devicePixelRatio: window.devicePixelRatio,
builder: (bytes) => Image.memory(bytes), builder: (bytes) => Image.memory(bytes),
); );
} }

View file

@ -100,7 +100,7 @@ packages:
name: flutter_svg name: flutter_svg
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.14.3" version: "0.14.4"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -175,7 +175,7 @@ packages:
name: pdf name: pdf
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.23" version: "1.3.24"
pedantic: pedantic:
dependency: transitive dependency: transitive
description: description:
@ -189,7 +189,7 @@ packages:
name: permission_handler name: permission_handler
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.3.0" version: "4.0.0"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -203,7 +203,7 @@ packages:
name: photo_view name: photo_view
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.0" version: "0.8.0"
printing: printing:
dependency: "direct main" dependency: "direct main"
description: description: