mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-26 08:36:50 +08:00
feat: ✨ SVG Rendering, Image theming and Image caption
fixes issues #405 #410 #246
This commit is contained in:

committed by
Vishesh Handa

parent
27f314eb65
commit
24cdc236e1
@ -16,6 +16,73 @@ settings:
|
|||||||
title: Display Settings
|
title: Display Settings
|
||||||
homeScreen: Home Screen
|
homeScreen: Home Screen
|
||||||
theme: Theme
|
theme: Theme
|
||||||
|
images:
|
||||||
|
title: Image Settings
|
||||||
|
subtitle: Configure how images are displayed
|
||||||
|
imageTextType:
|
||||||
|
alt: Alt Text
|
||||||
|
altAndTooltip: Alt Text and Tooltip
|
||||||
|
tooltip: Tooltip
|
||||||
|
none: None
|
||||||
|
captions:
|
||||||
|
title: Image Captions
|
||||||
|
subtitle: Configure the image captions
|
||||||
|
useAsCaption: Use as caption
|
||||||
|
overlayCaption: Draw caption on top of large enough images
|
||||||
|
transparentCaption: Overlay captions have a semitransparent background
|
||||||
|
blurBehindCaption: Blur Image behind caption
|
||||||
|
tooltipFirst:
|
||||||
|
title: Show tooltip before alt text
|
||||||
|
tooltip: Current order is “<tooltip> - <altText>”
|
||||||
|
altText: Current order is “<altText> - <tooltip>”
|
||||||
|
captionOverrides: Caption Overrides
|
||||||
|
tagDescription: Put these tags in “” to override the behavior for it.
|
||||||
|
doNotCaptionTags:
|
||||||
|
hint: DoNotCaption-Tags
|
||||||
|
label: Never use as caption with tags
|
||||||
|
validator:
|
||||||
|
empty: Tags cannot be empty.
|
||||||
|
same: Tag cannot be identical to a “DoCaption-Tag”.
|
||||||
|
doCaptionTags:
|
||||||
|
hint: DoCaption-Tags
|
||||||
|
label: Always use as caption with tags
|
||||||
|
validator:
|
||||||
|
empty: Tags cannot be empty.
|
||||||
|
same: Tag cannot be identical to a “DoNotCaption-Tag”.
|
||||||
|
theming:
|
||||||
|
title: Image Theming
|
||||||
|
subtitle: Configure how images are themed
|
||||||
|
adjustColors:
|
||||||
|
all: All
|
||||||
|
blackAndWhite: Only black and white
|
||||||
|
grays: Only grays
|
||||||
|
doNotThemeTags:
|
||||||
|
hint: DoNotTheme-Tags
|
||||||
|
label: Never theme images with tags
|
||||||
|
validator:
|
||||||
|
empty: Tags cannot be empty.
|
||||||
|
same: Tag cannot be identical to a “DoTheme-Tag”.
|
||||||
|
doThemeTags:
|
||||||
|
hint: DoTheme-Tag
|
||||||
|
label: Always theme images with tags
|
||||||
|
validator:
|
||||||
|
empty: Tags cannot be empty.
|
||||||
|
same: Tag cannot be identical to a “DoNotTheme-Tag”.
|
||||||
|
matchCanvasColor:
|
||||||
|
title: Match Background Color
|
||||||
|
subtitle: Replaces white/black parts of vector graphics with the canvas color
|
||||||
|
tagDescription: Put these tags in “” to override the behavior for the image.
|
||||||
|
themeOverrides: Theme Overrides
|
||||||
|
themeOverrideTagLocation: Theme Override Tag Location
|
||||||
|
themeRasterGraphics: Theme Raster Graphics (.png/.jpg)
|
||||||
|
themeSvgWithBackground: Theme SVGs With Background
|
||||||
|
themeVectorGraphics:
|
||||||
|
title: Theme Vector Graphics
|
||||||
|
filter: Using Color Filter
|
||||||
|
off: No
|
||||||
|
on: Yes
|
||||||
|
vectorGraphics: Vector Graphics (.svg)
|
||||||
|
vectorGraphicsAdjustColors: Colors to Adjust
|
||||||
theme:
|
theme:
|
||||||
light: Light
|
light: Light
|
||||||
dark: Dark
|
dark: Dark
|
||||||
@ -240,6 +307,9 @@ widgets:
|
|||||||
two: "{} Notes link to this Note"
|
two: "{} Notes link to this Note"
|
||||||
few: "{} Notes link to this Note"
|
few: "{} Notes link to this Note"
|
||||||
other: "{} Notes link to this Note"
|
other: "{} Notes link to this Note"
|
||||||
|
imageRenderer:
|
||||||
|
caption: "{first} - {second}"
|
||||||
|
httpError: "Code: {status} for {url}"
|
||||||
SortingOrderSelector:
|
SortingOrderSelector:
|
||||||
title: Sorting Criteria
|
title: Sorting Criteria
|
||||||
PurchaseButton:
|
PurchaseButton:
|
||||||
|
77
lib/screens/settings_display_images.dart
Normal file
77
lib/screens/settings_display_images.dart
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020-2021 Roland Fredenhagen <important@van-fredenhagen.de>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
import 'package:gitjournal/screens/settings_display_images_caption.dart';
|
||||||
|
import 'package:gitjournal/screens/settings_display_images_theming.dart';
|
||||||
|
|
||||||
|
class SettingsDisplayImagesScreen extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
SettingsDisplayImagesScreenState createState() =>
|
||||||
|
SettingsDisplayImagesScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsDisplayImagesScreenState
|
||||||
|
extends State<SettingsDisplayImagesScreen> {
|
||||||
|
final doNotThemeTagsKey = GlobalKey<FormFieldState<String>>();
|
||||||
|
final doThemeTagsKey = GlobalKey<FormFieldState<String>>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final body = ListView(children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Text(tr("settings.display.images.theming.title")),
|
||||||
|
subtitle: Text(tr("settings.display.images.theming.subtitle")),
|
||||||
|
onTap: () {
|
||||||
|
var route = MaterialPageRoute(
|
||||||
|
builder: (context) => SettingsDisplayImagesThemingScreen(),
|
||||||
|
settings:
|
||||||
|
const RouteSettings(name: '/settings/display_images/theming'),
|
||||||
|
);
|
||||||
|
Navigator.of(context).push(route);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(tr("settings.display.images.captions.title")),
|
||||||
|
subtitle: Text(tr("settings.display.images.captions.subtitle")),
|
||||||
|
onTap: () {
|
||||||
|
var route = MaterialPageRoute(
|
||||||
|
builder: (context) => SettingsDisplayImagesCaptionScreen(),
|
||||||
|
settings:
|
||||||
|
const RouteSettings(name: '/settings/display_images/caption'),
|
||||||
|
);
|
||||||
|
Navigator.of(context).push(route);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(tr('settings.display.images.title')),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
191
lib/screens/settings_display_images_caption.dart
Normal file
191
lib/screens/settings_display_images_caption.dart
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020-2021 Roland Fredenhagen <important@van-fredenhagen.de>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gitjournal/screens/settings_screen.dart';
|
||||||
|
import 'package:gitjournal/screens/settings_widgets.dart';
|
||||||
|
import 'package:gitjournal/settings.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class SettingsDisplayImagesCaptionScreen extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
SettingsDisplayImagesCaptionScreenState createState() =>
|
||||||
|
SettingsDisplayImagesCaptionScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsDisplayImagesCaptionScreenState
|
||||||
|
extends State<SettingsDisplayImagesCaptionScreen> {
|
||||||
|
final doNotCaptionTagsKey = GlobalKey<FormFieldState<String>>();
|
||||||
|
final doCaptionTagsKey = GlobalKey<FormFieldState<String>>();
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var settings = Provider.of<Settings>(context);
|
||||||
|
var saveDoNotCaptionTag = (String doNotCaptionTags) {
|
||||||
|
settings.doNotCaptionTags = parseTags(doNotCaptionTags);
|
||||||
|
settings.save();
|
||||||
|
};
|
||||||
|
var doNotCaptionTagsForm = Form(
|
||||||
|
child: TextFormField(
|
||||||
|
key: doNotCaptionTagsKey,
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText:
|
||||||
|
tr('settings.display.images.captions.doNotCaptionTags.hint'),
|
||||||
|
labelText:
|
||||||
|
tr('settings.display.images.captions.doNotCaptionTags.label'),
|
||||||
|
),
|
||||||
|
validator: (String value) {
|
||||||
|
value = value.trim();
|
||||||
|
if (parseTags(value).isEmpty) {
|
||||||
|
return tr(
|
||||||
|
'settings.display.images.captions.doNotCaptionTags.validator.empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseTags(value)
|
||||||
|
.intersection(settings.doCaptionTags)
|
||||||
|
.isNotEmpty) {
|
||||||
|
return tr(
|
||||||
|
'settings.display.images.captions.doNotCaptionTags.validator.same');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onFieldSubmitted: saveDoNotCaptionTag,
|
||||||
|
onSaved: saveDoNotCaptionTag,
|
||||||
|
initialValue: csvTags(settings.doNotCaptionTags),
|
||||||
|
),
|
||||||
|
onChanged: () {
|
||||||
|
if (!doNotCaptionTagsKey.currentState.validate()) return;
|
||||||
|
saveDoNotCaptionTag(doNotCaptionTagsKey.currentState.value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
var saveDoThemeTag = (String doCaptionTags) {
|
||||||
|
settings.doCaptionTags = parseTags(doCaptionTags);
|
||||||
|
settings.save();
|
||||||
|
doNotCaptionTagsForm.createState();
|
||||||
|
};
|
||||||
|
var doCaptionTagsForm = Form(
|
||||||
|
child: TextFormField(
|
||||||
|
key: doCaptionTagsKey,
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: tr('settings.display.images.captions.doCaptionTags.hint'),
|
||||||
|
labelText: tr('settings.display.images.captions.doCaptionTags.label'),
|
||||||
|
),
|
||||||
|
validator: (String value) {
|
||||||
|
if (parseTags(value).isEmpty) {
|
||||||
|
return tr(
|
||||||
|
'settings.display.images.captions.doCaptionTags.validator.empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseTags(value)
|
||||||
|
.intersection(settings.doNotCaptionTags)
|
||||||
|
.isNotEmpty) {
|
||||||
|
return tr(
|
||||||
|
'settings.display.images.captions.doCaptionTags.validator.same');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onFieldSubmitted: saveDoThemeTag,
|
||||||
|
onSaved: saveDoThemeTag,
|
||||||
|
initialValue: csvTags(settings.doCaptionTags),
|
||||||
|
),
|
||||||
|
onChanged: () {
|
||||||
|
if (!doCaptionTagsKey.currentState.validate()) return;
|
||||||
|
saveDoThemeTag(doCaptionTagsKey.currentState.value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
var body = ListView(children: <Widget>[
|
||||||
|
ListPreference(
|
||||||
|
title: tr("settings.display.images.captions.useAsCaption"),
|
||||||
|
currentOption: settings.useAsCaption.toPublicString(),
|
||||||
|
options: SettingsImageTextType.options
|
||||||
|
.map((e) => e.toPublicString())
|
||||||
|
.toList(),
|
||||||
|
onChange: (String publicStr) {
|
||||||
|
settings.useAsCaption =
|
||||||
|
SettingsImageTextType.fromPublicString(publicStr);
|
||||||
|
settings.save();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(tr('settings.display.images.captions.overlayCaption')),
|
||||||
|
value: settings.overlayCaption,
|
||||||
|
onChanged: (bool newVal) {
|
||||||
|
settings.overlayCaption = newVal;
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (settings.overlayCaption)
|
||||||
|
SwitchListTile(
|
||||||
|
title:
|
||||||
|
Text(tr('settings.display.images.captions.transparentCaption')),
|
||||||
|
value: settings.transparentCaption,
|
||||||
|
onChanged: (bool newVal) {
|
||||||
|
settings.transparentCaption = newVal;
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (settings.overlayCaption && settings.transparentCaption)
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(tr('settings.display.images.captions.blurBehindCaption')),
|
||||||
|
value: settings.blurBehindCaption,
|
||||||
|
onChanged: (bool newVal) {
|
||||||
|
settings.blurBehindCaption = newVal;
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(tr('settings.display.images.captions.tooltipFirst.title')),
|
||||||
|
value: settings.tooltipFirst,
|
||||||
|
subtitle: settings.tooltipFirst
|
||||||
|
? Text(tr('settings.display.images.captions.tooltipFirst.tooltip'))
|
||||||
|
: Text(tr('settings.display.images.captions.tooltipFirst.altText')),
|
||||||
|
onChanged: (bool newVal) {
|
||||||
|
settings.tooltipFirst = newVal;
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SettingsHeader(tr('settings.display.images.captions.captionOverrides')),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||||
|
child: Text(tr("settings.display.images.captions.tagDescription")),
|
||||||
|
),
|
||||||
|
ListTile(title: doCaptionTagsForm),
|
||||||
|
ListTile(title: doNotCaptionTagsForm)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(tr('settings.display.images.captions.title')),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
210
lib/screens/settings_display_images_theming.dart
Normal file
210
lib/screens/settings_display_images_theming.dart
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020-2021 Roland Fredenhagen <important@van-fredenhagen.de>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:gitjournal/screens/settings_widgets.dart';
|
||||||
|
import 'package:gitjournal/screens/settings_screen.dart';
|
||||||
|
import 'package:gitjournal/settings.dart';
|
||||||
|
|
||||||
|
class SettingsDisplayImagesThemingScreen extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
SettingsDisplayImagesThemingScreenState createState() =>
|
||||||
|
SettingsDisplayImagesThemingScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsDisplayImagesThemingScreenState
|
||||||
|
extends State<SettingsDisplayImagesThemingScreen> {
|
||||||
|
final doNotThemeTagsKey = GlobalKey<FormFieldState<String>>();
|
||||||
|
final doThemeTagsKey = GlobalKey<FormFieldState<String>>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var settings = Provider.of<Settings>(context);
|
||||||
|
|
||||||
|
var saveDoNotThemeTag = (String doNotThemeTags) {
|
||||||
|
settings.doNotThemeTags = parseTags(doNotThemeTags);
|
||||||
|
settings.save();
|
||||||
|
};
|
||||||
|
var doNotThemeTagsForm = Form(
|
||||||
|
child: TextFormField(
|
||||||
|
key: doNotThemeTagsKey,
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: tr('settings.display.images.theming.doNotThemeTags.hint'),
|
||||||
|
labelText: tr('settings.display.images.theming.doNotThemeTags.label'),
|
||||||
|
),
|
||||||
|
validator: (String value) {
|
||||||
|
value = value.trim();
|
||||||
|
if (parseTags(value).isEmpty) {
|
||||||
|
return tr(
|
||||||
|
'settings.display.images.theming.doNotThemeTags.validator.empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseTags(value).intersection(settings.doThemeTags).isNotEmpty) {
|
||||||
|
return tr(
|
||||||
|
'settings.display.images.theming.doNotThemeTags.validator.same');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onFieldSubmitted: saveDoNotThemeTag,
|
||||||
|
onSaved: saveDoNotThemeTag,
|
||||||
|
initialValue: csvTags(settings.doNotThemeTags),
|
||||||
|
),
|
||||||
|
onChanged: () {
|
||||||
|
if (!doNotThemeTagsKey.currentState.validate()) return;
|
||||||
|
saveDoNotThemeTag(doNotThemeTagsKey.currentState.value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
var saveDoThemeTag = (String doThemeTags) {
|
||||||
|
settings.doThemeTags = parseTags(doThemeTags);
|
||||||
|
settings.save();
|
||||||
|
};
|
||||||
|
var doThemeTagsForm = Form(
|
||||||
|
child: TextFormField(
|
||||||
|
key: doThemeTagsKey,
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: tr('settings.display.images.theming.doThemeTags.hint'),
|
||||||
|
labelText: tr('settings.display.images.theming.doThemeTags.label'),
|
||||||
|
),
|
||||||
|
validator: (String value) {
|
||||||
|
if (parseTags(value).isEmpty) {
|
||||||
|
return tr(
|
||||||
|
'settings.display.images.theming.doThemeTags.validator.empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseTags(value)
|
||||||
|
.intersection(settings.doNotThemeTags)
|
||||||
|
.isNotEmpty) {
|
||||||
|
return tr(
|
||||||
|
'settings.display.images.theming.doThemeTags.validator.same');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onFieldSubmitted: saveDoThemeTag,
|
||||||
|
onSaved: saveDoThemeTag,
|
||||||
|
initialValue: csvTags(settings.doThemeTags),
|
||||||
|
),
|
||||||
|
onChanged: () {
|
||||||
|
if (!doThemeTagsKey.currentState.validate()) return;
|
||||||
|
saveDoThemeTag(doThemeTagsKey.currentState.value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
var body = ListView(children: <Widget>[
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(tr('settings.display.images.theming.themeRasterGraphics')),
|
||||||
|
value: settings.themeRasterGraphics,
|
||||||
|
onChanged: (bool newVal) {
|
||||||
|
settings.themeRasterGraphics = newVal;
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SettingsHeader(tr('settings.display.images.theming.themeOverrides')),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||||
|
child: Text(tr("settings.display.images.theming.tagDescription")),
|
||||||
|
),
|
||||||
|
ListPreference(
|
||||||
|
title: tr("settings.display.images.theming.themeOverrideTagLocation"),
|
||||||
|
currentOption: settings.themeOverrideTagLocation.toPublicString(),
|
||||||
|
options: SettingsImageTextType.options
|
||||||
|
.map((e) => e.toPublicString())
|
||||||
|
.toList(),
|
||||||
|
onChange: (String publicStr) {
|
||||||
|
settings.themeOverrideTagLocation =
|
||||||
|
SettingsImageTextType.fromPublicString(publicStr);
|
||||||
|
settings.save();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(title: doThemeTagsForm),
|
||||||
|
ListTile(title: doNotThemeTagsForm),
|
||||||
|
SettingsHeader(tr('settings.display.images.theming.vectorGraphics')),
|
||||||
|
ListPreference(
|
||||||
|
title: tr("settings.display.images.theming.themeVectorGraphics.title"),
|
||||||
|
currentOption: settings.themeVectorGraphics.toPublicString(),
|
||||||
|
options: SettingsThemeVectorGraphics.options
|
||||||
|
.map((e) => e.toPublicString())
|
||||||
|
.toList(),
|
||||||
|
onChange: (String publicStr) {
|
||||||
|
settings.themeVectorGraphics =
|
||||||
|
SettingsThemeVectorGraphics.fromPublicString(publicStr);
|
||||||
|
settings.save();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (settings.themeVectorGraphics == SettingsThemeVectorGraphics.On)
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(
|
||||||
|
tr('settings.display.images.theming.themeSvgWithBackground')),
|
||||||
|
value: settings.themeSvgWithBackground,
|
||||||
|
onChanged: (bool newVal) {
|
||||||
|
settings.themeSvgWithBackground = newVal;
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (settings.themeVectorGraphics == SettingsThemeVectorGraphics.On)
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(
|
||||||
|
tr('settings.display.images.theming.matchCanvasColor.title')),
|
||||||
|
subtitle: Text(
|
||||||
|
tr('settings.display.images.theming.matchCanvasColor.subtitle')),
|
||||||
|
value: settings.matchCanvasColor,
|
||||||
|
onChanged: (bool newVal) {
|
||||||
|
settings.matchCanvasColor = newVal;
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (settings.themeVectorGraphics == SettingsThemeVectorGraphics.On)
|
||||||
|
ListPreference(
|
||||||
|
title:
|
||||||
|
tr("settings.display.images.theming.vectorGraphicsAdjustColors"),
|
||||||
|
currentOption: settings.vectorGraphicsAdjustColors.toPublicString(),
|
||||||
|
options: SettingsVectorGraphicsAdjustColors.options
|
||||||
|
.map((e) => e.toPublicString())
|
||||||
|
.toList(),
|
||||||
|
onChange: (String publicStr) {
|
||||||
|
settings.vectorGraphicsAdjustColors =
|
||||||
|
SettingsVectorGraphicsAdjustColors.fromPublicString(publicStr);
|
||||||
|
settings.save();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(tr('settings.display.images.theming.title')),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020-2021 Vishesh Handa <me@vhanda.in>
|
Copyright 2020-2021 Vishesh Handa <me@vhanda.in>
|
||||||
|
Roland Fredenhagen <important@van-fredenhagen.de>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -37,6 +38,7 @@ import 'package:gitjournal/repository_manager.dart';
|
|||||||
import 'package:gitjournal/screens/debug_screen.dart';
|
import 'package:gitjournal/screens/debug_screen.dart';
|
||||||
import 'package:gitjournal/screens/feature_timeline_screen.dart';
|
import 'package:gitjournal/screens/feature_timeline_screen.dart';
|
||||||
import 'package:gitjournal/screens/settings_bottom_menu_bar.dart';
|
import 'package:gitjournal/screens/settings_bottom_menu_bar.dart';
|
||||||
|
import 'package:gitjournal/screens/settings_display_images.dart';
|
||||||
import 'package:gitjournal/screens/settings_editors.dart';
|
import 'package:gitjournal/screens/settings_editors.dart';
|
||||||
import 'package:gitjournal/screens/settings_experimental.dart';
|
import 'package:gitjournal/screens/settings_experimental.dart';
|
||||||
import 'package:gitjournal/screens/settings_git_remote.dart';
|
import 'package:gitjournal/screens/settings_git_remote.dart';
|
||||||
@ -185,6 +187,17 @@ class SettingsListState extends State<SettingsList> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(tr("settings.display.images.title")),
|
||||||
|
subtitle: Text(tr("settings.display.images.subtitle")),
|
||||||
|
onTap: () {
|
||||||
|
var route = MaterialPageRoute(
|
||||||
|
builder: (context) => SettingsDisplayImagesScreen(),
|
||||||
|
settings: const RouteSettings(name: '/settings/display_images'),
|
||||||
|
);
|
||||||
|
Navigator.of(context).push(route);
|
||||||
|
},
|
||||||
|
),
|
||||||
ProOverlay(
|
ProOverlay(
|
||||||
feature: Feature.customizeHomeScreen,
|
feature: Feature.customizeHomeScreen,
|
||||||
child: ListPreference(
|
child: ListPreference(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020-2021 Vishesh Handa <me@vhanda.in>
|
Copyright 2020-2021 Vishesh Handa <me@vhanda.in>
|
||||||
|
Roland Fredenhagen <important@van-fredenhagen.de>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -69,6 +70,28 @@ class Settings extends ChangeNotifier {
|
|||||||
SettingsHomeScreen homeScreen = SettingsHomeScreen.Default;
|
SettingsHomeScreen homeScreen = SettingsHomeScreen.Default;
|
||||||
SettingsTheme theme = SettingsTheme.Default;
|
SettingsTheme theme = SettingsTheme.Default;
|
||||||
|
|
||||||
|
// Display - Image - Theming
|
||||||
|
bool themeRasterGraphics = false;
|
||||||
|
SettingsImageTextType themeOverrideTagLocation =
|
||||||
|
SettingsImageTextType.Default;
|
||||||
|
Set<String> doNotThemeTags = {"notheme", "!nt"};
|
||||||
|
Set<String> doThemeTags = {"dotheme", "!dt"};
|
||||||
|
SettingsThemeVectorGraphics themeVectorGraphics =
|
||||||
|
SettingsThemeVectorGraphics.Default;
|
||||||
|
bool themeSvgWithBackground = false;
|
||||||
|
bool matchCanvasColor = true;
|
||||||
|
SettingsVectorGraphicsAdjustColors vectorGraphicsAdjustColors =
|
||||||
|
SettingsVectorGraphicsAdjustColors.Default;
|
||||||
|
|
||||||
|
// Display - Image - Caption
|
||||||
|
bool overlayCaption = true;
|
||||||
|
bool transparentCaption = true;
|
||||||
|
bool blurBehindCaption = true;
|
||||||
|
bool tooltipFirst = false;
|
||||||
|
SettingsImageTextType useAsCaption = SettingsImageTextType.Default;
|
||||||
|
Set<String> doNotCaptionTags = {"nocaption", "!nc"};
|
||||||
|
Set<String> doCaptionTags = {"docaption", "!dc"};
|
||||||
|
|
||||||
SettingsMarkdownDefaultView markdownDefaultView =
|
SettingsMarkdownDefaultView markdownDefaultView =
|
||||||
SettingsMarkdownDefaultView.Default;
|
SettingsMarkdownDefaultView.Default;
|
||||||
SettingsMarkdownDefaultView markdownLastUsedView =
|
SettingsMarkdownDefaultView markdownLastUsedView =
|
||||||
@ -148,6 +171,35 @@ class Settings extends ChangeNotifier {
|
|||||||
SettingsHomeScreen.fromInternalString(_getString(pref, "homeScreen"));
|
SettingsHomeScreen.fromInternalString(_getString(pref, "homeScreen"));
|
||||||
theme = SettingsTheme.fromInternalString(_getString(pref, "theme"));
|
theme = SettingsTheme.fromInternalString(_getString(pref, "theme"));
|
||||||
|
|
||||||
|
// Display - Image - Theming
|
||||||
|
themeRasterGraphics =
|
||||||
|
_getBool(pref, "themeRasterGraphics") ?? themeRasterGraphics;
|
||||||
|
themeOverrideTagLocation = SettingsImageTextType.fromInternalString(
|
||||||
|
_getString(pref, "themeOverrideTagLocation"));
|
||||||
|
doNotThemeTags = _getStringSet(pref, "doNotThemeTags") ?? doNotThemeTags;
|
||||||
|
doThemeTags = _getStringSet(pref, "doThemeTags") ?? doThemeTags;
|
||||||
|
themeVectorGraphics = SettingsThemeVectorGraphics.fromInternalString(
|
||||||
|
_getString(pref, "themeVectorGraphics"));
|
||||||
|
themeSvgWithBackground =
|
||||||
|
_getBool(pref, "themeSvgWithBackground") ?? themeSvgWithBackground;
|
||||||
|
matchCanvasColor = _getBool(pref, "matchCanvasColor") ?? matchCanvasColor;
|
||||||
|
vectorGraphicsAdjustColors =
|
||||||
|
SettingsVectorGraphicsAdjustColors.fromInternalString(
|
||||||
|
_getString(pref, "vectorGraphicsAdjustColors"));
|
||||||
|
|
||||||
|
// Display - Image - Caption
|
||||||
|
overlayCaption = _getBool(pref, "overlayCaption") ?? overlayCaption;
|
||||||
|
transparentCaption =
|
||||||
|
_getBool(pref, "transparentCaption") ?? transparentCaption;
|
||||||
|
blurBehindCaption =
|
||||||
|
_getBool(pref, "blurBehindCaption") ?? blurBehindCaption;
|
||||||
|
tooltipFirst = _getBool(pref, "tooltipFirst") ?? tooltipFirst;
|
||||||
|
useAsCaption = SettingsImageTextType.fromInternalString(
|
||||||
|
_getString(pref, "useAsCaption"));
|
||||||
|
doNotCaptionTags =
|
||||||
|
_getStringSet(pref, "doNotCaptionTag") ?? doNotCaptionTags;
|
||||||
|
doCaptionTags = _getStringSet(pref, "doCaptionTag") ?? doCaptionTags;
|
||||||
|
|
||||||
imageLocationSpec =
|
imageLocationSpec =
|
||||||
_getString(pref, "imageLocationSpec") ?? imageLocationSpec;
|
_getString(pref, "imageLocationSpec") ?? imageLocationSpec;
|
||||||
|
|
||||||
@ -184,6 +236,10 @@ class Settings extends ChangeNotifier {
|
|||||||
return pref.getStringList(id + '_' + key);
|
return pref.getStringList(id + '_' + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<String> _getStringSet(SharedPreferences pref, String key) {
|
||||||
|
return _getStringList(pref, key)?.toSet();
|
||||||
|
}
|
||||||
|
|
||||||
int _getInt(SharedPreferences pref, String key) {
|
int _getInt(SharedPreferences pref, String key) {
|
||||||
return pref.getInt(id + '_' + key);
|
return pref.getInt(id + '_' + key);
|
||||||
}
|
}
|
||||||
@ -255,6 +311,49 @@ class Settings extends ChangeNotifier {
|
|||||||
defaultSet.homeScreen.toInternalString());
|
defaultSet.homeScreen.toInternalString());
|
||||||
await _setString(pref, "theme", theme.toInternalString(),
|
await _setString(pref, "theme", theme.toInternalString(),
|
||||||
defaultSet.theme.toInternalString());
|
defaultSet.theme.toInternalString());
|
||||||
|
|
||||||
|
// Display - Image - Theme
|
||||||
|
await _setBool(pref, "themeRasterGraphics", themeRasterGraphics,
|
||||||
|
defaultSet.themeRasterGraphics);
|
||||||
|
await _setString(
|
||||||
|
pref,
|
||||||
|
"themeOverrideTagLocation",
|
||||||
|
themeOverrideTagLocation.toInternalString(),
|
||||||
|
defaultSet.themeOverrideTagLocation.toInternalString());
|
||||||
|
await _setStringSet(
|
||||||
|
pref, "doNotThemeTags", doNotThemeTags, defaultSet.doNotThemeTags);
|
||||||
|
await _setStringSet(
|
||||||
|
pref, "doThemeTags", doThemeTags, defaultSet.doThemeTags);
|
||||||
|
await _setString(
|
||||||
|
pref,
|
||||||
|
"themeVectorGraphics",
|
||||||
|
themeVectorGraphics.toInternalString(),
|
||||||
|
defaultSet.themeVectorGraphics.toInternalString());
|
||||||
|
await _setBool(pref, "themeSvgWithBackground", themeSvgWithBackground,
|
||||||
|
defaultSet.themeSvgWithBackground);
|
||||||
|
await _setBool(pref, "matchCanvasColor", matchCanvasColor,
|
||||||
|
defaultSet.matchCanvasColor);
|
||||||
|
await _setString(
|
||||||
|
pref,
|
||||||
|
"vectorGraphicsAdjustColors",
|
||||||
|
vectorGraphicsAdjustColors.toInternalString(),
|
||||||
|
defaultSet.vectorGraphicsAdjustColors.toInternalString());
|
||||||
|
|
||||||
|
// Display - Image - Caption
|
||||||
|
await _setBool(
|
||||||
|
pref, "overlayCaption", overlayCaption, defaultSet.overlayCaption);
|
||||||
|
await _setBool(pref, "transparentCaption", transparentCaption,
|
||||||
|
defaultSet.transparentCaption);
|
||||||
|
await _setBool(pref, "blurBehindCaption", blurBehindCaption,
|
||||||
|
defaultSet.blurBehindCaption);
|
||||||
|
await _setBool(pref, "tooltipFirst", tooltipFirst, defaultSet.tooltipFirst);
|
||||||
|
await _setString(pref, "useAsCaption", useAsCaption.toInternalString(),
|
||||||
|
defaultSet.useAsCaption.toInternalString());
|
||||||
|
await _setStringSet(
|
||||||
|
pref, "doNotCaptionTag", doNotCaptionTags, defaultSet.doNotCaptionTags);
|
||||||
|
await _setStringSet(
|
||||||
|
pref, "doCaptionTag", doCaptionTags, defaultSet.doCaptionTags);
|
||||||
|
|
||||||
await _setString(pref, "imageLocationSpec", imageLocationSpec,
|
await _setString(pref, "imageLocationSpec", imageLocationSpec,
|
||||||
defaultSet.imageLocationSpec);
|
defaultSet.imageLocationSpec);
|
||||||
await _setBool(pref, "zenMode", zenMode, defaultSet.zenMode);
|
await _setBool(pref, "zenMode", zenMode, defaultSet.zenMode);
|
||||||
@ -372,6 +471,25 @@ class Settings extends ChangeNotifier {
|
|||||||
'markdownLastUsedView': markdownLastUsedView.toInternalString(),
|
'markdownLastUsedView': markdownLastUsedView.toInternalString(),
|
||||||
'homeScreen': homeScreen.toInternalString(),
|
'homeScreen': homeScreen.toInternalString(),
|
||||||
'theme': theme.toInternalString(),
|
'theme': theme.toInternalString(),
|
||||||
|
// Display - Image - Theming
|
||||||
|
'themeRasterGraphics': themeRasterGraphics.toString(),
|
||||||
|
'themeOverrideTagLocation': themeOverrideTagLocation.toInternalString(),
|
||||||
|
'doNotThemeTags': csvTags(doNotThemeTags),
|
||||||
|
'doThemeTags': csvTags(doThemeTags),
|
||||||
|
'themeVectorGraphics': themeVectorGraphics.toInternalString(),
|
||||||
|
'themeSvgWithBackground': themeSvgWithBackground.toString(),
|
||||||
|
'matchCanvasColor': matchCanvasColor.toString(),
|
||||||
|
'vectorGraphicsAdjustColors':
|
||||||
|
vectorGraphicsAdjustColors.toInternalString(),
|
||||||
|
// Display - Image - Caption
|
||||||
|
'overlayCaption': overlayCaption.toString(),
|
||||||
|
'transparentCaption': transparentCaption.toString(),
|
||||||
|
'blurBehindCaption': blurBehindCaption.toString(),
|
||||||
|
'tooltipFirst': tooltipFirst.toString(),
|
||||||
|
'useAsCaption': useAsCaption.toInternalString(),
|
||||||
|
'doNotCaptionTag': csvTags(doNotCaptionTags),
|
||||||
|
'doCaptionTag': csvTags(doCaptionTags),
|
||||||
|
//
|
||||||
'imageLocationSpec': imageLocationSpec,
|
'imageLocationSpec': imageLocationSpec,
|
||||||
'zenMode': zenMode.toString(),
|
'zenMode': zenMode.toString(),
|
||||||
'titleSettings': titleSettings.toInternalString(),
|
'titleSettings': titleSettings.toInternalString(),
|
||||||
@ -427,7 +545,7 @@ class NoteFileNameFormat {
|
|||||||
static const FromTitle =
|
static const FromTitle =
|
||||||
NoteFileNameFormat("FromTitle", 'settings.NoteFileNameFormat.title');
|
NoteFileNameFormat("FromTitle", 'settings.NoteFileNameFormat.title');
|
||||||
static const SimpleDate =
|
static const SimpleDate =
|
||||||
NoteFileNameFormat("SimpleDate", 'settings.NoteFileNameFormat.simmple');
|
NoteFileNameFormat("SimpleDate", 'settings.NoteFileNameFormat.simple');
|
||||||
static const UuidV4 =
|
static const UuidV4 =
|
||||||
NoteFileNameFormat("uuidv4", 'settings.NoteFileNameFormat.uuid');
|
NoteFileNameFormat("uuidv4", 'settings.NoteFileNameFormat.uuid');
|
||||||
static const Zettelkasten = NoteFileNameFormat(
|
static const Zettelkasten = NoteFileNameFormat(
|
||||||
@ -804,6 +922,169 @@ class SettingsHomeScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SettingsImageTextType {
|
||||||
|
static const AltTool = SettingsImageTextType(
|
||||||
|
"settings.display.images.imageTextType.altAndTooltip", "alt_and_tooltip");
|
||||||
|
static const Tooltip = SettingsImageTextType(
|
||||||
|
"settings.display.images.imageTextType.tooltip", "tooltip");
|
||||||
|
static const Alt =
|
||||||
|
SettingsImageTextType("settings.display.images.imageTextType.alt", "alt");
|
||||||
|
static const None = SettingsImageTextType(
|
||||||
|
"settings.display.images.imageTextType.none", "none");
|
||||||
|
static const Default = AltTool;
|
||||||
|
|
||||||
|
final String _str;
|
||||||
|
final String _publicString;
|
||||||
|
const SettingsImageTextType(this._publicString, this._str);
|
||||||
|
|
||||||
|
String toInternalString() {
|
||||||
|
return _str;
|
||||||
|
}
|
||||||
|
|
||||||
|
String toPublicString() {
|
||||||
|
return tr(_publicString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const options = <SettingsImageTextType>[
|
||||||
|
AltTool,
|
||||||
|
Tooltip,
|
||||||
|
Alt,
|
||||||
|
None,
|
||||||
|
];
|
||||||
|
|
||||||
|
static SettingsImageTextType fromInternalString(String str) {
|
||||||
|
for (var opt in options) {
|
||||||
|
if (opt.toInternalString() == str) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SettingsImageTextType fromPublicString(String str) {
|
||||||
|
for (var opt in options) {
|
||||||
|
if (opt.toPublicString() == str) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
assert(false,
|
||||||
|
"SettingsThemeOverrideTagLocation toString should never be called");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsThemeVectorGraphics {
|
||||||
|
static const On = SettingsThemeVectorGraphics(
|
||||||
|
"settings.display.images.theming.themeVectorGraphics.on", "on");
|
||||||
|
static const Off = SettingsThemeVectorGraphics(
|
||||||
|
"settings.display.images.theming.themeVectorGraphics.off", "off");
|
||||||
|
static const Filter = SettingsThemeVectorGraphics(
|
||||||
|
"settings.display.images.theming.themeVectorGraphics.filter", "filter");
|
||||||
|
static const Default = On;
|
||||||
|
|
||||||
|
final String _str;
|
||||||
|
final String _publicString;
|
||||||
|
const SettingsThemeVectorGraphics(this._publicString, this._str);
|
||||||
|
|
||||||
|
String toInternalString() {
|
||||||
|
return _str;
|
||||||
|
}
|
||||||
|
|
||||||
|
String toPublicString() {
|
||||||
|
return tr(_publicString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const options = <SettingsThemeVectorGraphics>[
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
Filter,
|
||||||
|
];
|
||||||
|
|
||||||
|
static SettingsThemeVectorGraphics fromInternalString(String str) {
|
||||||
|
for (var opt in options) {
|
||||||
|
if (opt.toInternalString() == str) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SettingsThemeVectorGraphics fromPublicString(String str) {
|
||||||
|
for (var opt in options) {
|
||||||
|
if (opt.toPublicString() == str) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
assert(
|
||||||
|
false, "SettingsThemeVectorGraphics toString should never be called");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsVectorGraphicsAdjustColors {
|
||||||
|
static const All = SettingsVectorGraphicsAdjustColors(
|
||||||
|
"settings.display.images.theming.adjustColors.all", "all");
|
||||||
|
static const BnW = SettingsVectorGraphicsAdjustColors(
|
||||||
|
"settings.display.images.theming.adjustColors.blackAndWhite",
|
||||||
|
"black_and_white");
|
||||||
|
static const Grays = SettingsVectorGraphicsAdjustColors(
|
||||||
|
"settings.display.images.theming.adjustColors.grays", "grays");
|
||||||
|
static const Default = All;
|
||||||
|
|
||||||
|
final String _str;
|
||||||
|
final String _publicString;
|
||||||
|
const SettingsVectorGraphicsAdjustColors(this._publicString, this._str);
|
||||||
|
|
||||||
|
String toInternalString() {
|
||||||
|
return _str;
|
||||||
|
}
|
||||||
|
|
||||||
|
String toPublicString() {
|
||||||
|
return tr(_publicString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const options = <SettingsVectorGraphicsAdjustColors>[
|
||||||
|
BnW,
|
||||||
|
Grays,
|
||||||
|
All,
|
||||||
|
];
|
||||||
|
|
||||||
|
static SettingsVectorGraphicsAdjustColors fromInternalString(String str) {
|
||||||
|
for (var opt in options) {
|
||||||
|
if (opt.toInternalString() == str) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SettingsVectorGraphicsAdjustColors fromPublicString(String str) {
|
||||||
|
for (var opt in options) {
|
||||||
|
if (opt.toPublicString() == str) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
assert(false,
|
||||||
|
"SettingsVectorGraphicsAdjustColors toString should never be called");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String generateRandomId() {
|
String generateRandomId() {
|
||||||
return Uuid().v4().substring(0, 8);
|
return Uuid().v4().substring(0, 8);
|
||||||
}
|
}
|
||||||
@ -920,3 +1201,16 @@ class SettingsTitle {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<String> parseTags(String tags) {
|
||||||
|
return tags
|
||||||
|
.toLowerCase()
|
||||||
|
.split(",")
|
||||||
|
.map((e) => e.trim())
|
||||||
|
.where((e) => e.isNotEmpty)
|
||||||
|
.toSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
String csvTags(Set<String> tags) {
|
||||||
|
return tags.join(", ");
|
||||||
|
}
|
||||||
|
57
lib/utils/hero_dialog.dart
Normal file
57
lib/utils/hero_dialog.dart
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020-2021 Roland Fredenhagen <important@van-fredenhagen.de>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class HeroDialogRoute<T> extends PageRoute<T> {
|
||||||
|
HeroDialogRoute({this.builder}) : super();
|
||||||
|
|
||||||
|
final WidgetBuilder builder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get opaque => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get barrierDismissible => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get transitionDuration => const Duration(milliseconds: 200);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get maintainState => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get barrierColor => Colors.black54;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildTransitions(BuildContext context, Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation, Widget child) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut),
|
||||||
|
child: child);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildPage(BuildContext context, Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation) {
|
||||||
|
return builder(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get barrierLabel => "close preview";
|
||||||
|
}
|
479
lib/widgets/image_renderer.dart
Normal file
479
lib/widgets/image_renderer.dart
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020-2021 Roland Fredenhagen <important@van-fredenhagen.de>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:gitjournal/utils/logger.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:gitjournal/settings.dart';
|
||||||
|
import 'package:gitjournal/utils/hero_dialog.dart';
|
||||||
|
|
||||||
|
class ThemableImage extends StatelessWidget {
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
final String altText;
|
||||||
|
final String tooltip;
|
||||||
|
final Future<dynamic> data;
|
||||||
|
|
||||||
|
ThemableImage._(this.data, this.width, this.height, altText, tooltip)
|
||||||
|
: altText = altText ?? "",
|
||||||
|
tooltip = tooltip ?? "";
|
||||||
|
|
||||||
|
factory ThemableImage(Uri uri, String imageDirectory,
|
||||||
|
{double width, double height, String altText, String titel}) {
|
||||||
|
final file = ((uri.isScheme("http") || uri.isScheme("https"))
|
||||||
|
? DefaultCacheManager().getSingleFile(uri.toString())
|
||||||
|
: Future.sync(
|
||||||
|
() => File.fromUri(Uri.parse(imageDirectory + uri.toString()))));
|
||||||
|
|
||||||
|
final data = file.then(
|
||||||
|
(value) => value.path.endsWith(".svg") ? value.readAsString() : file);
|
||||||
|
|
||||||
|
return ThemableImage._(data, width, height, altText, titel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final settings = Provider.of<Settings>(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final dark = theme.brightness == Brightness.dark;
|
||||||
|
|
||||||
|
ThemeOverride override = ThemeOverride.None;
|
||||||
|
if (settings.themeOverrideTagLocation == SettingsImageTextType.Alt ||
|
||||||
|
settings.themeOverrideTagLocation == SettingsImageTextType.AltTool) {
|
||||||
|
if (hasTag(altText, settings.doThemeTags)) {
|
||||||
|
override = ThemeOverride.Do;
|
||||||
|
} else if (hasTag(altText, settings.doNotThemeTags)) {
|
||||||
|
override = ThemeOverride.No;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (settings.themeOverrideTagLocation == SettingsImageTextType.Tooltip ||
|
||||||
|
settings.themeOverrideTagLocation == SettingsImageTextType.AltTool) {
|
||||||
|
if (hasTag(tooltip, settings.doThemeTags)) {
|
||||||
|
override = ThemeOverride.Do;
|
||||||
|
} else if (hasTag(tooltip, settings.doNotThemeTags)) {
|
||||||
|
override = ThemeOverride.No;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final small =
|
||||||
|
constraints.maxWidth < MediaQuery.of(context).size.width - 40;
|
||||||
|
final image = FutureBuilder(
|
||||||
|
future: data,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
String errorMessage = snapshot.error.toString();
|
||||||
|
Log.e(errorMessage);
|
||||||
|
if (snapshot.error is HttpExceptionWithStatus) {
|
||||||
|
final httpError = snapshot.error as HttpExceptionWithStatus;
|
||||||
|
errorMessage = tr("widgets.imageRenderer.httpError",
|
||||||
|
namedArgs: {
|
||||||
|
"status": httpError.statusCode.toString(),
|
||||||
|
"url": httpError.uri.toString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.error,
|
||||||
|
color: theme.errorColor,
|
||||||
|
size: 36,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
errorMessage,
|
||||||
|
style: theme.textTheme.bodyText1
|
||||||
|
.copyWith(color: theme.errorColor),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
Widget im;
|
||||||
|
if (snapshot.data is String) {
|
||||||
|
im = _handleSvg(
|
||||||
|
snapshot.data, width, height, context, override);
|
||||||
|
} else {
|
||||||
|
im = Image.file(snapshot.data, width: width, height: height);
|
||||||
|
if ((settings.themeRasterGraphics ||
|
||||||
|
override == ThemeOverride.Do) &&
|
||||||
|
override != ThemeOverride.No &&
|
||||||
|
dark) {
|
||||||
|
im = themeFilter(im, theme.canvasColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (small) {
|
||||||
|
return GestureDetector(
|
||||||
|
child: Hero(tag: im, child: im),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
HeroDialogRoute(
|
||||||
|
builder: (context) => GestureDetector(
|
||||||
|
child: Hero(tag: im, child: im),
|
||||||
|
onTap: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return im;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Center(child: CircularProgressIndicator())));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (small || !settings.overlayCaption) {
|
||||||
|
final caption = _imageCaption(context, altText, tooltip, false);
|
||||||
|
return Column(children: [image, if (caption != null) caption]);
|
||||||
|
}
|
||||||
|
final caption = _imageCaption(context, altText, tooltip, true);
|
||||||
|
return Stack(
|
||||||
|
alignment: AlignmentDirectional.bottomEnd,
|
||||||
|
children: [image, if (caption != null) caption]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _cleanCaption(BuildContext context, String caption) {
|
||||||
|
final settings = Provider.of<Settings>(context);
|
||||||
|
final tags = [
|
||||||
|
...settings.doThemeTags,
|
||||||
|
...settings.doNotThemeTags,
|
||||||
|
...settings.doCaptionTags,
|
||||||
|
...settings.doNotCaptionTags
|
||||||
|
];
|
||||||
|
return caption
|
||||||
|
.replaceAll(
|
||||||
|
RegExp(
|
||||||
|
r"\s*(?<=\s|\b)(" +
|
||||||
|
tags.map(RegExp.escape).join("|") +
|
||||||
|
r")(?=\s|\b)\s*",
|
||||||
|
caseSensitive: false),
|
||||||
|
" ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _imageCaption(
|
||||||
|
BuildContext context, String altText, String tooltip, bool overlay) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final settings = Provider.of<Settings>(context);
|
||||||
|
final dark = theme.brightness == Brightness.dark;
|
||||||
|
|
||||||
|
bool altTextCaption =
|
||||||
|
settings.useAsCaption == SettingsImageTextType.AltTool ||
|
||||||
|
settings.useAsCaption == SettingsImageTextType.AltTool;
|
||||||
|
if (hasTag(altText, settings.doCaptionTags)) {
|
||||||
|
altTextCaption = true;
|
||||||
|
} else if (hasTag(altText, settings.doNotCaptionTags)) {
|
||||||
|
altTextCaption = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tooltipCaption =
|
||||||
|
settings.useAsCaption == SettingsImageTextType.Tooltip ||
|
||||||
|
settings.useAsCaption == SettingsImageTextType.AltTool;
|
||||||
|
if (hasTag(tooltip, settings.doCaptionTags)) {
|
||||||
|
tooltipCaption = true;
|
||||||
|
} else if (hasTag(tooltip, settings.doNotCaptionTags)) {
|
||||||
|
tooltipCaption = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
altText = altTextCaption ? _cleanCaption(context, altText) : "";
|
||||||
|
tooltip = tooltipCaption ? _cleanCaption(context, tooltip) : "";
|
||||||
|
String text = "";
|
||||||
|
if (altText.isNotEmpty && tooltip.isNotEmpty) {
|
||||||
|
text = tr("widgets.imageRenderer.caption",
|
||||||
|
namedArgs: settings.tooltipFirst
|
||||||
|
? {"first": tooltip, "second": altText}
|
||||||
|
: {"first": altText, "second": tooltip});
|
||||||
|
} else {
|
||||||
|
text = altText + tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
if (!overlay) {
|
||||||
|
return Text(text, style: theme.textTheme.caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
final maxWidth = constraints.constrainWidth(200);
|
||||||
|
const padding = 6.0, margin = 4.0, borderRadius = 5.0;
|
||||||
|
final blur = settings.blurBehindCaption ? 2.0 : 0.0;
|
||||||
|
|
||||||
|
final overflown = (TextPainter(
|
||||||
|
text: TextSpan(text: text),
|
||||||
|
maxLines: 2,
|
||||||
|
textScaleFactor: MediaQuery.of(context).textScaleFactor,
|
||||||
|
textDirection: Directionality.of(context))
|
||||||
|
..layout(maxWidth: maxWidth - 2 * (padding + margin)))
|
||||||
|
.didExceedMaxLines;
|
||||||
|
|
||||||
|
final bColor = settings.transparentCaption
|
||||||
|
? (dark ? Colors.black : Colors.white).withAlpha(150)
|
||||||
|
: theme.canvasColor;
|
||||||
|
final box = ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
|
||||||
|
child: Container(
|
||||||
|
color: bColor,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(padding),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: theme.textTheme.bodyText2,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!overflown) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||||
|
child: Container(margin: const EdgeInsets.all(margin), child: box));
|
||||||
|
}
|
||||||
|
|
||||||
|
final caption = Hero(tag: "caption", child: box);
|
||||||
|
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(margin),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(context, HeroDialogRoute(builder: (context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: caption,
|
||||||
|
));
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
child: caption,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _handleSvg(final String string, double width, final double height,
|
||||||
|
final BuildContext context, final ThemeOverride override) {
|
||||||
|
final settings = Provider.of<Settings>(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
width ??= MediaQuery.of(context).size.width;
|
||||||
|
final dark = theme.brightness == Brightness.dark;
|
||||||
|
if (settings.themeVectorGraphics == SettingsThemeVectorGraphics.Off &&
|
||||||
|
override != ThemeOverride.Do ||
|
||||||
|
override == ThemeOverride.No) {
|
||||||
|
return SvgPicture.string(string, width: width, height: height);
|
||||||
|
}
|
||||||
|
if (settings.themeVectorGraphics == SettingsThemeVectorGraphics.Filter &&
|
||||||
|
dark) {
|
||||||
|
return themeFilter(SvgPicture.string(string, width: width, height: height),
|
||||||
|
theme.canvasColor);
|
||||||
|
}
|
||||||
|
final transformColor = dark
|
||||||
|
? themeDark(theme.canvasColor, settings.vectorGraphicsAdjustColors)
|
||||||
|
: settings.matchCanvasColor
|
||||||
|
? whiteToCanvas(theme.canvasColor)
|
||||||
|
: noTheme;
|
||||||
|
return SvgPicture(
|
||||||
|
StringPicture((data, colorFilter, key) async {
|
||||||
|
DrawableRoot svgRoot = await svg.fromSvgString(data, key);
|
||||||
|
if (settings.themeSvgWithBackground ||
|
||||||
|
override == ThemeOverride.Do ||
|
||||||
|
!hasBackground(svgRoot, svgRoot.viewport.viewBox.width,
|
||||||
|
svgRoot.viewport.viewBox.height)) {
|
||||||
|
svgRoot = themeDrawable(svgRoot, transformColor);
|
||||||
|
}
|
||||||
|
final Picture pic = svgRoot.toPicture(
|
||||||
|
clipToViewBox: false,
|
||||||
|
colorFilter: colorFilter,
|
||||||
|
);
|
||||||
|
return PictureInfo(
|
||||||
|
picture: pic,
|
||||||
|
viewport: svgRoot.viewport.viewBoxRect,
|
||||||
|
size: svgRoot.viewport.size,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
string +
|
||||||
|
'<?theme darkMode="$dark" ' +
|
||||||
|
'override="$override" ' +
|
||||||
|
'opaqueBackground="${settings.themeSvgWithBackground}" ' +
|
||||||
|
'whiteToCanvas="${settings.matchCanvasColor}" ' +
|
||||||
|
'adjustColors="${settings.vectorGraphicsAdjustColors.toInternalString()}"?>'),
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color Function(Color color) whiteToCanvas(Color canvasColor) =>
|
||||||
|
(Color color) => color.value == 0xffffffff ? canvasColor : color;
|
||||||
|
|
||||||
|
Color noTheme(Color color) => color;
|
||||||
|
|
||||||
|
Color Function(Color color) themeDark(
|
||||||
|
Color canvasColor, SettingsVectorGraphicsAdjustColors adjustColors) =>
|
||||||
|
(Color color) {
|
||||||
|
final hslColor = HSLColor.fromColor(color);
|
||||||
|
final backGroundLightness = HSLColor.fromColor(canvasColor).lightness;
|
||||||
|
if (adjustColors == SettingsVectorGraphicsAdjustColors.BnW) {
|
||||||
|
if (hslColor.lightness > 0.95 && hslColor.saturation < 0.02) {
|
||||||
|
return HSLColor.fromAHSL(hslColor.alpha, 0, 0, backGroundLightness)
|
||||||
|
.toColor();
|
||||||
|
}
|
||||||
|
if (hslColor.lightness < 0.02) {
|
||||||
|
return HSLColor.fromAHSL(hslColor.alpha, 0, 0, 1).toColor();
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
if (adjustColors == SettingsVectorGraphicsAdjustColors.Grays) {
|
||||||
|
if (hslColor.saturation < 0.02 || hslColor.lightness < 0.02) {
|
||||||
|
return HSLColor.fromAHSL(hslColor.alpha, 0, 0,
|
||||||
|
1 - (hslColor.lightness * (1 - backGroundLightness)))
|
||||||
|
.toColor();
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HSLColor.fromAHSL(
|
||||||
|
hslColor.alpha,
|
||||||
|
hslColor.hue,
|
||||||
|
hslColor.saturation,
|
||||||
|
1 - (hslColor.lightness * (1 - backGroundLightness)))
|
||||||
|
.toColor();
|
||||||
|
};
|
||||||
|
|
||||||
|
bool hasBackground(Drawable draw, double width, double height) {
|
||||||
|
if (draw is DrawableShape) {
|
||||||
|
final drawShape = draw;
|
||||||
|
return drawShape.style.fill != null &&
|
||||||
|
drawShape.style.fill.color.alpha > 0.99 &&
|
||||||
|
[
|
||||||
|
Offset(0 + width * 0.01, 0 + height * 0.01),
|
||||||
|
Offset(width - width * 0.01, 0 + height * 0.01),
|
||||||
|
Offset(width - width * 0.01, height - height * 0.01),
|
||||||
|
Offset(0 + width * 0.01, height - height * 0.01),
|
||||||
|
].every(drawShape.path.contains);
|
||||||
|
}
|
||||||
|
if (draw is DrawableParent) {
|
||||||
|
final drawParent = draw;
|
||||||
|
return drawParent.children
|
||||||
|
.any((element) => hasBackground(element, width, height));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasTag(String text, Set<String> tags) {
|
||||||
|
return tags
|
||||||
|
.map((e) => RegExp(r"(?<=\s|\b)" + RegExp.escape(e) + r"(?=\s|\b)",
|
||||||
|
caseSensitive: false))
|
||||||
|
.any((e) => e.hasMatch(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
Drawable themeDrawable(
|
||||||
|
Drawable draw, Color Function(Color color) transformColor) {
|
||||||
|
if (draw is DrawableStyleable && !(draw is DrawableGroup)) {
|
||||||
|
final DrawableStyleable drawStylable = draw;
|
||||||
|
draw = drawStylable.mergeStyle(DrawableStyle(
|
||||||
|
stroke: drawStylable.style.stroke != null &&
|
||||||
|
drawStylable.style.stroke.color != null
|
||||||
|
? DrawablePaint.merge(
|
||||||
|
DrawablePaint(drawStylable.style.stroke.style,
|
||||||
|
color: transformColor(drawStylable.style.stroke.color)),
|
||||||
|
drawStylable.style.stroke)
|
||||||
|
: null,
|
||||||
|
fill: drawStylable.style.fill != null &&
|
||||||
|
drawStylable.style.fill.color != null
|
||||||
|
? DrawablePaint.merge(
|
||||||
|
DrawablePaint(drawStylable.style.fill.style,
|
||||||
|
color: transformColor(drawStylable.style.fill.color)),
|
||||||
|
drawStylable.style.fill)
|
||||||
|
: null));
|
||||||
|
}
|
||||||
|
if (draw is DrawableParent) {
|
||||||
|
final DrawableParent drawParent = draw;
|
||||||
|
final children = drawParent.children
|
||||||
|
.map((e) => themeDrawable(e, transformColor))
|
||||||
|
.toList(growable: false);
|
||||||
|
if (draw is DrawableRoot) {
|
||||||
|
final DrawableRoot drawRoot = draw;
|
||||||
|
draw = DrawableRoot(drawRoot.id, drawRoot.viewport, children,
|
||||||
|
drawRoot.definitions, drawRoot.style,
|
||||||
|
transform: drawRoot.transform);
|
||||||
|
} else if (draw is DrawableGroup) {
|
||||||
|
final DrawableGroup drawGroup = draw;
|
||||||
|
draw = DrawableGroup(drawGroup.id, children, drawGroup.style,
|
||||||
|
transform: drawGroup.transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return draw;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget themeFilter(Widget widget, Color background) {
|
||||||
|
final lightness = (1 - HSLColor.fromColor(background).lightness) * 255;
|
||||||
|
// TODO Switch to HSL Filter, when availible (https://github.com/flutter/flutter/issues/76729)
|
||||||
|
final stack = ColorFiltered(
|
||||||
|
colorFilter: ColorFilter.matrix(<double>[
|
||||||
|
-(lightness / 255), 0, 0, 0, 255, // R
|
||||||
|
0, -(lightness / 255), 0, 0, 255, // G
|
||||||
|
0, 0, -(lightness / 255), 0, 255, // B
|
||||||
|
0, 0, 0, 1, 0 // A
|
||||||
|
]),
|
||||||
|
child: widget);
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ThemeOverride { None, Do, No }
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020-2021 Vishesh Handa <me@vhanda.in>
|
Copyright 2020-2021 Vishesh Handa <me@vhanda.in>
|
||||||
|
Roland Fredenhagen <important@van-fredenhagen.de>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -14,12 +15,9 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:function_types/function_types.dart';
|
import 'package:function_types/function_types.dart';
|
||||||
@ -33,6 +31,7 @@ import 'package:gitjournal/folder_views/common.dart';
|
|||||||
import 'package:gitjournal/utils.dart';
|
import 'package:gitjournal/utils.dart';
|
||||||
import 'package:gitjournal/utils/link_resolver.dart';
|
import 'package:gitjournal/utils/link_resolver.dart';
|
||||||
import 'package:gitjournal/utils/logger.dart';
|
import 'package:gitjournal/utils/logger.dart';
|
||||||
|
import 'package:gitjournal/widgets/image_renderer.dart';
|
||||||
|
|
||||||
class MarkdownRenderer extends StatelessWidget {
|
class MarkdownRenderer extends StatelessWidget {
|
||||||
final Note note;
|
final Note note;
|
||||||
@ -59,7 +58,7 @@ class MarkdownRenderer extends StatelessWidget {
|
|||||||
var markdownStyleSheet = MarkdownStyleSheet.fromTheme(theme).copyWith(
|
var markdownStyleSheet = MarkdownStyleSheet.fromTheme(theme).copyWith(
|
||||||
code: theme.textTheme.bodyText2.copyWith(
|
code: theme.textTheme.bodyText2.copyWith(
|
||||||
backgroundColor: theme.dialogBackgroundColor,
|
backgroundColor: theme.dialogBackgroundColor,
|
||||||
fontFamily: "monospace",
|
fontFamily: 'monospace',
|
||||||
fontSize: theme.textTheme.bodyText2.fontSize * 0.85,
|
fontSize: theme.textTheme.bodyText2.fontSize * 0.85,
|
||||||
),
|
),
|
||||||
tableBorder: TableBorder.all(color: theme.highlightColor, width: 0),
|
tableBorder: TableBorder.all(color: theme.highlightColor, width: 0),
|
||||||
@ -111,15 +110,16 @@ class MarkdownRenderer extends StatelessWidget {
|
|||||||
try {
|
try {
|
||||||
await launch(link);
|
await launch(link);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
Log.e("Opening Link", ex: e, stacktrace: stackTrace);
|
Log.e('Opening Link', ex: e, stacktrace: stackTrace);
|
||||||
showSnackbar(
|
showSnackbar(
|
||||||
context,
|
context,
|
||||||
tr('widgets.NoteViewer.linkNotFound', args: [link]),
|
tr('widgets.NoteViewer.linkNotFound', args: [link]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
imageBuilder: (url, title, alt) => kDefaultImageBuilder(
|
imageBuilder: (url, title, alt) => ThemableImage(
|
||||||
url, note.parent.folderPath + p.separator, null, null),
|
url, note.parent.folderPath + p.separator,
|
||||||
|
titel: title, altText: alt),
|
||||||
extensionSet: markdownExtensions(),
|
extensionSet: markdownExtensions(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -134,91 +134,7 @@ class MarkdownRenderer extends StatelessWidget {
|
|||||||
markdownExtensions.inlineSyntaxes.insert(1, TaskListSyntax());
|
markdownExtensions.inlineSyntaxes.insert(1, TaskListSyntax());
|
||||||
return markdownExtensions;
|
return markdownExtensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Widget _buildFooter(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.arrow_left),
|
|
||||||
tooltip: 'Previous Entry',
|
|
||||||
onPressed: showPrevNoteFunc,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 10,
|
|
||||||
child: Text(''),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.arrow_right),
|
|
||||||
tooltip: 'Next Entry',
|
|
||||||
onPressed: showNextNoteFunc,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Copied from flutter_markdown
|
|
||||||
// But it uses CachedNetworkImage
|
|
||||||
//
|
|
||||||
typedef Widget ImageBuilder(
|
|
||||||
Uri uri, String imageDirectory, double width, double height);
|
|
||||||
|
|
||||||
final ImageBuilder kDefaultImageBuilder = (
|
|
||||||
Uri uri,
|
|
||||||
String imageDirectory,
|
|
||||||
double width,
|
|
||||||
double height,
|
|
||||||
) {
|
|
||||||
if (uri.scheme == 'http' || uri.scheme == 'https') {
|
|
||||||
return CachedNetworkImage(
|
|
||||||
imageUrl: uri.toString(),
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
placeholder: (context, url) => const CircularProgressIndicator(),
|
|
||||||
errorWidget: (context, url, error) => const Icon(Icons.error),
|
|
||||||
);
|
|
||||||
} else if (uri.scheme == 'data') {
|
|
||||||
return _handleDataSchemeUri(uri, width, height);
|
|
||||||
} else if (uri.scheme == "resource") {
|
|
||||||
return Image.asset(uri.path, width: width, height: height);
|
|
||||||
} else {
|
|
||||||
Uri fileUri = imageDirectory != null
|
|
||||||
? Uri.parse(imageDirectory + uri.toString())
|
|
||||||
: uri;
|
|
||||||
if (fileUri.scheme == 'http' || fileUri.scheme == 'https') {
|
|
||||||
return CachedNetworkImage(
|
|
||||||
imageUrl: fileUri.toString(),
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
placeholder: (context, url) => const CircularProgressIndicator(),
|
|
||||||
errorWidget: (context, url, error) => const Icon(Icons.error),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Image.file(File.fromUri(fileUri), width: width, height: height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Widget _handleDataSchemeUri(Uri uri, final double width, final double height) {
|
|
||||||
final String mimeType = uri.data.mimeType;
|
|
||||||
if (mimeType.startsWith('image/')) {
|
|
||||||
return Image.memory(
|
|
||||||
uri.data.contentAsBytes(),
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
);
|
|
||||||
} else if (mimeType.startsWith('text/')) {
|
|
||||||
return Text(uri.data.contentAsString());
|
|
||||||
}
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
/// Parse ==Words==
|
/// Parse ==Words==
|
||||||
|
Reference in New Issue
Block a user