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/widgets/common/data_providers/settings_provider.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/home_page.dart';
import 'package:aves/widgets/welcome_page.dart';
@ -37,34 +38,38 @@ class _AvesAppState extends State<AvesApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Aves',
theme: ThemeData(
brightness: Brightness.dark,
accentColor: accentColor,
scaffoldBackgroundColor: Colors.grey[900],
buttonColor: accentColor,
toggleableActiveColor: accentColor,
tooltipTheme: TooltipThemeData(
verticalOffset: 32,
),
appBarTheme: AppBarTheme(
textTheme: TextTheme(
headline6: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: 'Concourse Caps',
// place the settings provider above `MaterialApp`
// so it can be used during navigation transitions
return SettingsProvider(
child: MaterialApp(
title: 'Aves',
theme: ThemeData(
brightness: Brightness.dark,
accentColor: accentColor,
scaffoldBackgroundColor: Colors.grey[900],
buttonColor: accentColor,
toggleableActiveColor: accentColor,
tooltipTheme: TooltipThemeData(
verticalOffset: 32,
),
appBarTheme: AppBarTheme(
textTheme: TextTheme(
headline6: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: 'Concourse Caps',
),
),
),
),
),
home: FutureBuilder<void>(
future: _appSetup,
builder: (context, snapshot) {
if (snapshot.hasError) return Icon(AIcons.error);
if (snapshot.connectionState != ConnectionState.done) return Scaffold();
return settings.hasAcceptedTerms ? HomePage() : WelcomePage();
},
home: FutureBuilder<void>(
future: _appSetup,
builder: (context, snapshot) {
if (snapshot.hasError) return Icon(AIcons.error);
if (snapshot.connectionState != ConnectionState.done) return Scaffold();
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);
class Settings {
class Settings extends ChangeNotifier {
static SharedPreferences _prefs;
final ObserverList<SettingsCallback> _listeners = ObserverList<SettingsCallback>();
Settings._private();
// preferences
@ -28,6 +26,7 @@ class Settings {
static const infoMapZoomKey = 'info_map_zoom';
static const launchPageKey = 'launch_page';
static const coordinateFormatKey = 'coordinates_format';
static const svgBackgroundKey = 'svg_background';
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
@ -37,26 +36,6 @@ class Settings {
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) ?? '';
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
@ -93,6 +72,10 @@ class Settings {
set coordinateFormat(CoordinateFormat newValue) => setAndNotify(coordinateFormatKey, newValue.toString());
int get svgBackground => _prefs.getInt(svgBackgroundKey) ?? 0xFFFFFFFF;
set svgBackground(int newValue) => setAndNotify(svgBackgroundKey, newValue);
// convenience methods
// ignore: avoid_positional_boolean_parameters
@ -133,7 +116,7 @@ class Settings {
_prefs.setBool(key, 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 = [
Dependency(
name: 'CWAC-Document',

View file

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

View file

@ -1,8 +1,9 @@
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:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class ThumbnailVectorImage extends StatelessWidget {
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
width: extent,
height: extent,
child: SvgPicture(
UriPicture(
uri: entry.uri,
mimeType: entry.mimeType,
colorFilter: Constants.svgColorFilter,
),
width: extent,
height: extent,
child: Selector<Settings, int>(
selector: (context, s) => s.svgBackground,
builder: (context, svgBackground, child) {
final colorFilter = ColorFilter.mode(Color(svgBackground), BlendMode.dstOver);
return SvgPicture(
UriPicture(
uri: entry.uri,
mimeType: entry.mimeType,
colorFilter: colorFilter,
),
width: extent,
height: extent,
);
},
),
);
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/utils/constants.dart';
import 'package:aves/model/settings.dart';
import 'package:aves/widgets/album/empty.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
@ -77,12 +77,13 @@ class ImageView extends StatelessWidget {
Widget child;
if (entry.isSvg) {
final colorFilter = ColorFilter.mode(Color(settings.svgBackground), BlendMode.dstOver);
child = PhotoView.customChild(
child: SvgPicture(
UriPicture(
uri: entry.uri,
mimeType: entry.mimeType,
colorFilter: Constants.svgColorFilter,
colorFilter: colorFilter,
),
placeholderBuilder: (context) => loadingBuilder(context, fastThumbnailProvider),
),

View file

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

View file

@ -80,7 +80,7 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
final overlayContentMaxWidth = mqWidth - viewPadding.horizontal - innerPadding.horizontal;
return Container(
color: FullscreenOverlay.backgroundColor,
color: kOverlayBackgroundColor,
padding: viewInsets + viewPadding.copyWith(top: 0),
child: FutureBuilder<OverlayMetadata>(
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:flutter/material.dart';
class FullscreenOverlay {
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,
);
}
}
const kOverlayBackgroundColor = Colors.black26;
class OverlayButton extends StatelessWidget {
final Animation<double> scale;
@ -26,10 +17,10 @@ class OverlayButton extends StatelessWidget {
child: BlurredOval(
child: Material(
type: MaterialType.circle,
color: FullscreenOverlay.backgroundColor,
color: kOverlayBackgroundColor,
child: Ink(
decoration: BoxDecoration(
border: FullscreenOverlay.buildBorder(context),
border: AvesCircleBorder.build(context),
shape: BoxShape.circle,
),
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/utils/durations.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/icons.dart';
import 'package:aves/widgets/fullscreen/overlay/common.dart';
@ -180,8 +181,8 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
child: Container(
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16) + EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: FullscreenOverlay.backgroundColor,
border: FullscreenOverlay.buildBorder(context),
color: kOverlayBackgroundColor,
border: AvesCircleBorder.build(context),
borderRadius: BorderRadius.circular(progressBarBorderRadius),
),
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/settings/coordinate_format.dart';
import 'package:aves/widgets/settings/launch_page.dart';
import 'package:aves/widgets/settings/svg_background.dart';
import 'package:flutter/material.dart';
class SettingsPage extends StatelessWidget {
@ -27,6 +28,14 @@ class SettingsPage extends StatelessWidget {
Flexible(child: LaunchPageSelector()),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('SVG background:'),
SizedBox(width: 8),
Flexible(child: SvgBackgroundSelector()),
],
),
Row(
mainAxisSize: MainAxisSize.min,
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(() {});
},
);
}
}