Skip to content

Instantly share code, notes, and snippets.

@roipeker
Created February 20, 2025 13:51
Show Gist options
  • Save roipeker/69204d1e25767a686d26754f0a45848d to your computer and use it in GitHub Desktop.
Save roipeker/69204d1e25767a686d26754f0a45848d to your computer and use it in GitHub Desktop.
Svg with basic theme support.
// make SVG behave like Icons.
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/svg.dart';
import 'package:http/http.dart' as http;
import 'svg_icon_theme.dart';
class SvgIcon extends StatelessWidget {
final double? size;
final Color? color;
final BoxFit? fit;
final Alignment? alignment;
final bool? allowDrawingOutsideViewBox;
final BytesLoader bytesLoader;
SvgIcon.asset(
String asset, {
String? package,
SvgTheme? theme,
super.key,
this.size,
this.color,
this.fit,
this.alignment,
this.allowDrawingOutsideViewBox,
}) : bytesLoader = SvgAssetLoader(
asset,
packageName: package,
theme: theme,
);
SvgIcon.network(
String url, {
Map<String, String>? headers,
http.Client? httpClient,
SvgTheme? theme,
super.key,
this.size,
this.color,
this.fit,
this.alignment,
this.allowDrawingOutsideViewBox,
}) : bytesLoader = SvgNetworkLoader(
url,
headers: headers,
theme: theme,
httpClient: httpClient,
);
SvgIcon.string(
String string, {
SvgTheme? theme,
super.key,
this.size,
this.color,
this.fit,
this.alignment,
this.allowDrawingOutsideViewBox,
}) : bytesLoader = SvgStringLoader(string, theme: theme);
@override
Widget build(BuildContext context) {
final iconThemeData = SvgIconTheme.maybeOf(context)?.data;
final effectiveSize = size ?? iconThemeData?.size;
final effectiveColor = color ?? iconThemeData?.color;
final effectiveFit = fit ?? iconThemeData?.fit;
final effectiveAlign = alignment ?? iconThemeData?.alignment;
final effectiveDrawingOutside =
allowDrawingOutsideViewBox ?? iconThemeData?.allowDrawingOutsideViewBox;
ColorFilter? filter;
if (effectiveColor != null) {
filter = ColorFilter.mode(
effectiveColor,
BlendMode.srcIn,
);
}
return SvgPicture(
bytesLoader,
key: key,
width: effectiveSize,
height: effectiveSize,
colorFilter: filter,
fit: effectiveFit ?? BoxFit.contain,
alignment: effectiveAlign ?? Alignment.center,
allowDrawingOutsideViewBox: effectiveDrawingOutside ?? false,
);
}
}
import 'dart:ui';
import 'package:flutter/widgets.dart';
class SvgIconThemeData {
const SvgIconThemeData({
this.color,
this.size,
this.fit,
this.alignment,
this.allowDrawingOutsideViewBox,
});
final Color? color;
final double? size;
final BoxFit? fit;
final Alignment? alignment;
final bool? allowDrawingOutsideViewBox;
const SvgIconThemeData.fallback()
: color = null,
size = null,
fit = null,
alignment = null,
allowDrawingOutsideViewBox = null;
SvgIconThemeData resolve(BuildContext context) => this;
factory SvgIconThemeData.fromIconTheme(IconThemeData iconThemeData) {
return SvgIconThemeData(
size: iconThemeData.size,
color: iconThemeData.color,
);
}
SvgIconThemeData merge(SvgIconThemeData? other) {
if (other == null) {
return this;
}
return copyWith(
size: other.size,
color: other.color,
fit: other.fit,
alignment: other.alignment,
allowDrawingOutsideViewBox: other.allowDrawingOutsideViewBox,
);
}
SvgIconThemeData copyWith({
final Color? color,
final double? size,
final BoxFit? fit,
final Alignment? alignment,
final bool? allowDrawingOutsideViewBox,
}) {
return SvgIconThemeData(
color: color ?? this.color,
size: size ?? this.size,
fit: fit ?? this.fit,
alignment: alignment ?? this.alignment,
allowDrawingOutsideViewBox: allowDrawingOutsideViewBox ?? this.allowDrawingOutsideViewBox,
);
}
static SvgIconThemeData lerp(SvgIconThemeData? a, SvgIconThemeData? b, double t) {
if (identical(a, b) && a != null) {
return a;
}
return SvgIconThemeData(
size: lerpDouble(a?.size, b?.size, t),
color: Color.lerp(a?.color, b?.color, t),
alignment: Alignment.lerp(a?.alignment, b?.alignment, t),
fit: b?.fit,
allowDrawingOutsideViewBox: b?.allowDrawingOutsideViewBox,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SvgIconThemeData &&
runtimeType == other.runtimeType &&
color == other.color &&
size == other.size &&
fit == other.fit &&
alignment == other.alignment &&
allowDrawingOutsideViewBox == other.allowDrawingOutsideViewBox;
@override
int get hashCode => Object.hash(color, size, fit, alignment, allowDrawingOutsideViewBox);
}
class SvgIconTheme extends InheritedWidget {
const SvgIconTheme({
super.key,
required this.data,
required super.child,
});
final SvgIconThemeData data;
static SvgIconTheme? maybeOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<SvgIconTheme>();
}
static SvgIconThemeData of(BuildContext context) {
// final SvgIconTheme? result = maybeOf(context) ?? const SvgIconThemeData.fallback();
final result = _getInheritedIconThemeData(context).resolve(context);
return result;
}
static SvgIconThemeData _getInheritedIconThemeData(BuildContext context) {
final theme = context.dependOnInheritedWidgetOfExactType<SvgIconTheme>();
return theme?.data ?? const SvgIconThemeData.fallback();
}
static Widget merge({
Key? key,
required SvgIconThemeData data,
required Widget child,
}) {
return Builder(
builder: (BuildContext context) {
return SvgIconTheme(
key: key,
data: _getInheritedIconThemeData(context).merge(data),
child: child,
);
},
);
}
Widget wrap(BuildContext context, Widget child) {
return SvgIconTheme(data: data, child: child);
}
@override
bool updateShouldNotify(SvgIconTheme oldWidget) => data != oldWidget.data;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment