settings: svg background

This commit is contained in:
Thibault Deckers 2020-08-30 11:53:31 +09:00
parent 5311caf494
commit 7a8e8503af
14 changed files with 149 additions and 82 deletions

View file

@ -1,4 +1,5 @@
import 'package:aves/model/settings.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/widgets/common/data_providers/settings_provider.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/home_page.dart'; import 'package:aves/widgets/home_page.dart';
import 'package:aves/widgets/welcome_page.dart'; import 'package:aves/widgets/welcome_page.dart';
@ -37,34 +38,38 @@ class _AvesAppState extends State<AvesApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( // place the settings provider above `MaterialApp`
title: 'Aves', // so it can be used during navigation transitions
theme: ThemeData( return SettingsProvider(
brightness: Brightness.dark, child: MaterialApp(
accentColor: accentColor, title: 'Aves',
scaffoldBackgroundColor: Colors.grey[900], theme: ThemeData(
buttonColor: accentColor, brightness: Brightness.dark,
toggleableActiveColor: accentColor, accentColor: accentColor,
tooltipTheme: TooltipThemeData( scaffoldBackgroundColor: Colors.grey[900],
verticalOffset: 32, buttonColor: accentColor,
), toggleableActiveColor: accentColor,
appBarTheme: AppBarTheme( tooltipTheme: TooltipThemeData(
textTheme: TextTheme( verticalOffset: 32,
headline6: TextStyle( ),
fontSize: 20, appBarTheme: AppBarTheme(
fontWeight: FontWeight.bold, textTheme: TextTheme(
fontFamily: 'Concourse Caps', headline6: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: 'Concourse Caps',
),
), ),
), ),
), ),
), home: FutureBuilder<void>(
home: FutureBuilder<void>( future: _appSetup,
future: _appSetup, builder: (context, snapshot) {
builder: (context, snapshot) { if (snapshot.hasError) return Icon(AIcons.error);
if (snapshot.hasError) return Icon(AIcons.error); if (snapshot.connectionState != ConnectionState.done) return Scaffold();
if (snapshot.connectionState != ConnectionState.done) return Scaffold(); return settings.hasAcceptedTerms ? HomePage() : WelcomePage();
return settings.hasAcceptedTerms ? HomePage() : WelcomePage(); },
}, ),
), ),
); );
} }

View file

@ -11,11 +11,9 @@ final Settings settings = Settings._private();
typedef SettingsCallback = void Function(String key, dynamic oldValue, dynamic newValue); typedef SettingsCallback = void Function(String key, dynamic oldValue, dynamic newValue);
class Settings { class Settings extends ChangeNotifier {
static SharedPreferences _prefs; static SharedPreferences _prefs;
final ObserverList<SettingsCallback> _listeners = ObserverList<SettingsCallback>();
Settings._private(); Settings._private();
// preferences // preferences
@ -28,6 +26,7 @@ class Settings {
static const infoMapZoomKey = 'info_map_zoom'; static const infoMapZoomKey = 'info_map_zoom';
static const launchPageKey = 'launch_page'; static const launchPageKey = 'launch_page';
static const coordinateFormatKey = 'coordinates_format'; static const coordinateFormatKey = 'coordinates_format';
static const svgBackgroundKey = 'svg_background';
Future<void> init() async { Future<void> init() async {
_prefs = await SharedPreferences.getInstance(); _prefs = await SharedPreferences.getInstance();
@ -37,26 +36,6 @@ class Settings {
return _prefs.clear(); return _prefs.clear();
} }
void addListener(SettingsCallback listener) => _listeners.add(listener);
void removeListener(SettingsCallback listener) => _listeners.remove(listener);
void notifyListeners(String key, dynamic oldValue, dynamic newValue) {
debugPrint('$runtimeType notifyListeners key=$key, old=$oldValue, new=$newValue');
if (_listeners != null) {
final localListeners = _listeners.toList();
for (final listener in localListeners) {
try {
if (_listeners.contains(listener)) {
listener(key, oldValue, newValue);
}
} catch (exception, stack) {
debugPrint('$runtimeType failed to notify listeners with exception=$exception\n$stack');
}
}
}
}
String get catalogTimeZone => _prefs.getString(catalogTimeZoneKey) ?? ''; String get catalogTimeZone => _prefs.getString(catalogTimeZoneKey) ?? '';
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue); set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
@ -93,6 +72,10 @@ class Settings {
set coordinateFormat(CoordinateFormat newValue) => setAndNotify(coordinateFormatKey, newValue.toString()); set coordinateFormat(CoordinateFormat newValue) => setAndNotify(coordinateFormatKey, newValue.toString());
int get svgBackground => _prefs.getInt(svgBackgroundKey) ?? 0xFFFFFFFF;
set svgBackground(int newValue) => setAndNotify(svgBackgroundKey, newValue);
// convenience methods // convenience methods
// ignore: avoid_positional_boolean_parameters // ignore: avoid_positional_boolean_parameters
@ -133,7 +116,7 @@ class Settings {
_prefs.setBool(key, newValue); _prefs.setBool(key, newValue);
} }
if (oldValue != newValue) { if (oldValue != newValue) {
notifyListeners(key, oldValue, newValue); notifyListeners();
} }
} }
} }

View file

@ -19,9 +19,6 @@ class Constants {
], ],
); );
static const svgBackground = Colors.white;
static const svgColorFilter = ColorFilter.mode(svgBackground, BlendMode.dstOver);
static const List<Dependency> androidDependencies = [ static const List<Dependency> androidDependencies = [
Dependency( Dependency(
name: 'CWAC-Document', name: 'CWAC-Document',

View file

@ -87,7 +87,8 @@ class SectionHeader extends StatelessWidget {
// force a higher first line to match leading icon/selector dimension // force a higher first line to match leading icon/selector dimension
style: TextStyle(height: 2.3 * textScaleFactor), style: TextStyle(height: 2.3 * textScaleFactor),
), // 23 hair spaces match a width of 40.0 ), // 23 hair spaces match a width of 40.0
if (hasTrailing) TextSpan(text: '\u200A' * 17), if (hasTrailing)
TextSpan(text: '\u200A' * 17),
TextSpan( TextSpan(
text: text, text: text,
style: Constants.titleTextStyle, style: Constants.titleTextStyle,

View file

@ -1,8 +1,9 @@
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class ThumbnailVectorImage extends StatelessWidget { class ThumbnailVectorImage extends StatelessWidget {
final ImageEntry entry; final ImageEntry entry;
@ -23,14 +24,20 @@ class ThumbnailVectorImage extends StatelessWidget {
// so that `SvgPicture` doesn't get aligned by the `Stack` like the overlay icons // so that `SvgPicture` doesn't get aligned by the `Stack` like the overlay icons
width: extent, width: extent,
height: extent, height: extent,
child: SvgPicture( child: Selector<Settings, int>(
UriPicture( selector: (context, s) => s.svgBackground,
uri: entry.uri, builder: (context, svgBackground, child) {
mimeType: entry.mimeType, final colorFilter = ColorFilter.mode(Color(svgBackground), BlendMode.dstOver);
colorFilter: Constants.svgColorFilter, return SvgPicture(
), UriPicture(
width: extent, uri: entry.uri,
height: extent, mimeType: entry.mimeType,
colorFilter: colorFilter,
),
width: extent,
height: extent,
);
},
), ),
); );
return heroTag == null return heroTag == null

View file

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class AvesCircleBorder {
static BoxBorder build(BuildContext context) {
final subPixel = MediaQuery.of(context).devicePixelRatio > 2;
return Border.all(
color: Colors.white30,
width: subPixel ? 0.5 : 1.0,
);
}
}

View file

@ -0,0 +1,17 @@
import 'package:aves/model/settings.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class SettingsProvider extends StatelessWidget {
final Widget child;
const SettingsProvider({@required this.child});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Settings>.value(
value: settings,
child: child,
);
}
}

View file

@ -1,5 +1,5 @@
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/widgets/album/empty.dart'; import 'package:aves/widgets/album/empty.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart'; import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
@ -77,12 +77,13 @@ class ImageView extends StatelessWidget {
Widget child; Widget child;
if (entry.isSvg) { if (entry.isSvg) {
final colorFilter = ColorFilter.mode(Color(settings.svgBackground), BlendMode.dstOver);
child = PhotoView.customChild( child = PhotoView.customChild(
child: SvgPicture( child: SvgPicture(
UriPicture( UriPicture(
uri: entry.uri, uri: entry.uri,
mimeType: entry.mimeType, mimeType: entry.mimeType,
colorFilter: Constants.svgColorFilter, colorFilter: colorFilter,
), ),
placeholderBuilder: (context) => loadingBuilder(context, fastThumbnailProvider), placeholderBuilder: (context) => loadingBuilder(context, fastThumbnailProvider),
), ),

View file

@ -1,6 +1,7 @@
import 'package:aves/model/settings.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/widgets/common/action_delegates/map_style_dialog.dart'; import 'package:aves/widgets/common/action_delegates/map_style_dialog.dart';
import 'package:aves/widgets/common/borders.dart';
import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/info/location_section.dart'; import 'package:aves/widgets/fullscreen/info/location_section.dart';
@ -115,10 +116,10 @@ class MapOverlayButton extends StatelessWidget {
return BlurredOval( return BlurredOval(
child: Material( child: Material(
type: MaterialType.circle, type: MaterialType.circle,
color: FullscreenOverlay.backgroundColor, color: kOverlayBackgroundColor,
child: Ink( child: Ink(
decoration: BoxDecoration( decoration: BoxDecoration(
border: FullscreenOverlay.buildBorder(context), border: AvesCircleBorder.build(context),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: IconButton( child: IconButton(

View file

@ -80,7 +80,7 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
final overlayContentMaxWidth = mqWidth - viewPadding.horizontal - innerPadding.horizontal; final overlayContentMaxWidth = mqWidth - viewPadding.horizontal - innerPadding.horizontal;
return Container( return Container(
color: FullscreenOverlay.backgroundColor, color: kOverlayBackgroundColor,
padding: viewInsets + viewPadding.copyWith(top: 0), padding: viewInsets + viewPadding.copyWith(top: 0),
child: FutureBuilder<OverlayMetadata>( child: FutureBuilder<OverlayMetadata>(
future: _detailLoader, future: _detailLoader,

View file

@ -1,17 +1,8 @@
import 'package:aves/widgets/common/borders.dart';
import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class FullscreenOverlay { const kOverlayBackgroundColor = Colors.black26;
static const backgroundColor = Colors.black26;
static BoxBorder buildBorder(BuildContext context) {
final subPixel = MediaQuery.of(context).devicePixelRatio > 2;
return Border.all(
color: Colors.white30,
width: subPixel ? 0.5 : 1.0,
);
}
}
class OverlayButton extends StatelessWidget { class OverlayButton extends StatelessWidget {
final Animation<double> scale; final Animation<double> scale;
@ -26,10 +17,10 @@ class OverlayButton extends StatelessWidget {
child: BlurredOval( child: BlurredOval(
child: Material( child: Material(
type: MaterialType.circle, type: MaterialType.circle,
color: FullscreenOverlay.backgroundColor, color: kOverlayBackgroundColor,
child: Ink( child: Ink(
decoration: BoxDecoration( decoration: BoxDecoration(
border: FullscreenOverlay.buildBorder(context), border: AvesCircleBorder.build(context),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: child, child: child,

View file

@ -4,6 +4,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/utils/durations.dart'; import 'package:aves/utils/durations.dart';
import 'package:aves/utils/time_utils.dart'; import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/borders.dart';
import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart';
@ -180,8 +181,8 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
child: Container( child: Container(
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16) + EdgeInsets.only(bottom: 16), padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16) + EdgeInsets.only(bottom: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: FullscreenOverlay.backgroundColor, color: kOverlayBackgroundColor,
border: FullscreenOverlay.buildBorder(context), border: AvesCircleBorder.build(context),
borderRadius: BorderRadius.circular(progressBarBorderRadius), borderRadius: BorderRadius.circular(progressBarBorderRadius),
), ),
child: Column( child: Column(

View file

@ -2,6 +2,7 @@ import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
import 'package:aves/widgets/settings/coordinate_format.dart'; import 'package:aves/widgets/settings/coordinate_format.dart';
import 'package:aves/widgets/settings/launch_page.dart'; import 'package:aves/widgets/settings/launch_page.dart';
import 'package:aves/widgets/settings/svg_background.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
@ -27,6 +28,14 @@ class SettingsPage extends StatelessWidget {
Flexible(child: LaunchPageSelector()), Flexible(child: LaunchPageSelector()),
], ],
), ),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('SVG background:'),
SizedBox(width: 8),
Flexible(child: SvgBackgroundSelector()),
],
),
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [

View file

@ -0,0 +1,43 @@
import 'package:aves/model/settings.dart';
import 'package:aves/widgets/common/borders.dart';
import 'package:flutter/material.dart';
class SvgBackgroundSelector extends StatefulWidget {
@override
_SvgBackgroundSelectorState createState() => _SvgBackgroundSelectorState();
}
class _SvgBackgroundSelectorState extends State<SvgBackgroundSelector> {
@override
Widget build(BuildContext context) {
const radius = 24.0;
return DropdownButton<int>(
items: [0xFFFFFFFF, 0xFF000000, 0x00000000].map((selected) {
return DropdownMenuItem(
value: selected,
child: Container(
height: radius,
width: radius,
decoration: BoxDecoration(
color: Color(selected),
border: AvesCircleBorder.build(context),
shape: BoxShape.circle,
),
child: selected == 0
? Icon(
Icons.clear,
size: 20,
color: Colors.white30,
)
: null,
),
);
}).toList(),
value: settings.svgBackground,
onChanged: (selected) {
settings.svgBackground = selected;
setState(() {});
},
);
}
}