improved fullscreen with notch, top overlay buttons
This commit is contained in:
parent
a25f81f359
commit
09dedaa604
4 changed files with 113 additions and 54 deletions
7
android/app/src/main/res/values-v28/styles.xml
Normal file
7
android/app/src/main/res/values-v28/styles.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="AppTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowTranslucentNavigation">@bool/translucentNavBar</item> <!-- API19+, tinted background & request the SYSTEM_UI_FLAG_LAYOUT_STABLE and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flags -->
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <!-- API28+, draws next to the notch in fullscreen -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
35
lib/widgets/common/blurred.dart
Normal file
35
lib/widgets/common/blurred.dart
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BlurredRect extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const BlurredRect({Key key, this.child}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ClipRect(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlurredOval extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const BlurredOval({Key key, this.child}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ClipOval(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info_page.dart';
|
import 'package:aves/widgets/fullscreen/info_page.dart';
|
||||||
import 'package:aves/widgets/fullscreen/overlay.dart';
|
import 'package:aves/widgets/fullscreen/overlay.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:photo_view/photo_view_gallery.dart';
|
import 'package:photo_view/photo_view_gallery.dart';
|
||||||
|
|
||||||
|
@ -69,7 +70,11 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
|
||||||
ImagePage(
|
ImagePage(
|
||||||
entries: entries,
|
entries: entries,
|
||||||
pageController: _horizontalPager,
|
pageController: _horizontalPager,
|
||||||
onTap: () => _overlayVisible.value = !_overlayVisible.value,
|
onTap: () {
|
||||||
|
final visible = !_overlayVisible.value;
|
||||||
|
_overlayVisible.value = visible;
|
||||||
|
SystemChrome.setEnabledSystemUIOverlays(visible ? []: SystemUiOverlay.values);
|
||||||
|
},
|
||||||
onPageChanged: (page) => setState(() => _currentHorizontalPage = page),
|
onPageChanged: (page) => setState(() => _currentHorizontalPage = page),
|
||||||
onScaleChanged: (state) => setState(() => _isInitialScale = state == PhotoViewScaleState.initial),
|
onScaleChanged: (state) => setState(() => _isInitialScale = state == PhotoViewScaleState.initial),
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,27 +4,12 @@ import 'dart:ui';
|
||||||
import 'package:aves/model/android_app_service.dart';
|
import 'package:aves/model/android_app_service.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/metadata_service.dart';
|
import 'package:aves/model/metadata_service.dart';
|
||||||
|
import 'package:aves/widgets/common/blurred.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
const kOverlayBackground = Colors.black26;
|
const kOverlayBackground = Colors.black26;
|
||||||
|
|
||||||
class Blurred extends StatelessWidget {
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
const Blurred({Key key, this.child}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ClipRect(
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FullscreenTopOverlay extends StatelessWidget {
|
class FullscreenTopOverlay extends StatelessWidget {
|
||||||
final List<ImageEntry> entries;
|
final List<ImageEntry> entries;
|
||||||
final int index;
|
final int index;
|
||||||
|
@ -35,23 +20,23 @@ class FullscreenTopOverlay extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Blurred(
|
return SafeArea(
|
||||||
child: SafeArea(
|
child: Padding(
|
||||||
child: Container(
|
padding: EdgeInsets.all(8.0),
|
||||||
height: kToolbarHeight,
|
child: Row(
|
||||||
child: AppBar(
|
children: [
|
||||||
title: Text('${index + 1}/${entries.length}'),
|
OverlayButton(
|
||||||
actions: [
|
child: BackButton(),
|
||||||
// IconButton(icon: Icon(Icons.delete), onPressed: delete),
|
),
|
||||||
IconButton(
|
Spacer(),
|
||||||
|
OverlayButton(
|
||||||
|
child: IconButton(
|
||||||
icon: Icon(Icons.share),
|
icon: Icon(Icons.share),
|
||||||
onPressed: share,
|
onPressed: share,
|
||||||
tooltip: 'Share',
|
tooltip: 'Share',
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
elevation: 0,
|
],
|
||||||
backgroundColor: kOverlayBackground,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -102,28 +87,31 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
|
||||||
final innerPadding = EdgeInsets.all(8.0);
|
final innerPadding = EdgeInsets.all(8.0);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final overlayContentMaxWidth = mediaQuery.size.width - mediaQuery.viewPadding.horizontal - innerPadding.horizontal;
|
final overlayContentMaxWidth = mediaQuery.size.width - mediaQuery.viewPadding.horizontal - innerPadding.horizontal;
|
||||||
return Blurred(
|
return BlurredRect(
|
||||||
child: IgnorePointer(
|
child: Container(
|
||||||
child: Padding(
|
color: kOverlayBackground,
|
||||||
padding: mediaQuery.viewInsets + mediaQuery.viewPadding.copyWith(top: 0),
|
child: IgnorePointer(
|
||||||
child: Container(
|
child: Padding(
|
||||||
padding: innerPadding,
|
padding: mediaQuery.viewInsets + mediaQuery.viewPadding.copyWith(top: 0),
|
||||||
color: kOverlayBackground,
|
child: Container(
|
||||||
child: FutureBuilder(
|
padding: innerPadding,
|
||||||
future: _detailLoader,
|
child: FutureBuilder(
|
||||||
builder: (futureContext, AsyncSnapshot<Map> snapshot) {
|
future: _detailLoader,
|
||||||
if (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) {
|
builder: (futureContext, AsyncSnapshot<Map> snapshot) {
|
||||||
_lastDetails = snapshot.data;
|
if (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) {
|
||||||
_lastEntry = entry;
|
_lastDetails = snapshot.data;
|
||||||
}
|
_lastEntry = entry;
|
||||||
return _lastEntry == null
|
}
|
||||||
? SizedBox.shrink()
|
return _lastEntry == null
|
||||||
: _FullscreenBottomOverlayContent(
|
? SizedBox.shrink()
|
||||||
entry: _lastEntry,
|
: _FullscreenBottomOverlayContent(
|
||||||
details: _lastDetails,
|
entry: _lastEntry,
|
||||||
maxWidth: overlayContentMaxWidth,
|
details: _lastDetails,
|
||||||
);
|
position: '${widget.index + 1}/${widget.entries.length}',
|
||||||
},
|
maxWidth: overlayContentMaxWidth,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -135,9 +123,10 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
|
||||||
class _FullscreenBottomOverlayContent extends StatelessWidget {
|
class _FullscreenBottomOverlayContent extends StatelessWidget {
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
final Map details;
|
final Map details;
|
||||||
|
final String position;
|
||||||
final double maxWidth;
|
final double maxWidth;
|
||||||
|
|
||||||
_FullscreenBottomOverlayContent({this.entry, this.details, this.maxWidth});
|
_FullscreenBottomOverlayContent({this.entry, this.details, this.position, this.maxWidth});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -159,7 +148,7 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: maxWidth,
|
width: maxWidth,
|
||||||
child: Text(
|
child: Text(
|
||||||
entry.title,
|
'$position – ${entry.title}',
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -196,3 +185,26 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OverlayButton extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const OverlayButton({this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlurredOval(
|
||||||
|
child: Material(
|
||||||
|
type: MaterialType.circle,
|
||||||
|
color: kOverlayBackground,
|
||||||
|
child: Ink(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.white30, width: 0.5),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue