Skip to content

Commit

Permalink
Custom fonts (#22)
Browse files Browse the repository at this point in the history
* Allow numeric BorderRadius values

* Custom fonts and fontFamily

* Remove typing warning
  • Loading branch information
FeodorFitsner authored Jun 12, 2022
1 parent 79ea819 commit 0d2bc15
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 97 deletions.
2 changes: 1 addition & 1 deletion client/lib/controls/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ImageControl extends StatelessWidget {
builder: (context, pageUri) {
return baseControl(
_clipCorners(
Image.network(getAssetUrl(pageUri!, src),
Image.network(getAssetUri(pageUri!, src).toString(),
width: width, height: height, repeat: repeat, fit: fit),
control),
parent,
Expand Down
184 changes: 103 additions & 81 deletions client/lib/controls/page.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flet_view/utils/user_fonts.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';

Expand All @@ -11,6 +12,7 @@ import '../utils/colors.dart';
import '../utils/desktop.dart';
import '../utils/edge_insets.dart';
import '../utils/theme.dart';
import '../utils/uri.dart';
import '../widgets/page_media.dart';
import 'app_bar.dart';
import 'create_control.dart';
Expand Down Expand Up @@ -110,89 +112,109 @@ class PageControl extends StatelessWidget {
childIds.add(appBar.id);
}

return StoreConnector<AppState, PageMediaViewModel>(
return StoreConnector<AppState, Uri?>(
distinct: true,
converter: (store) => PageMediaViewModel.fromStore(store),
builder: (context, media) {
var theme = themeMode == ThemeMode.light ||
(themeMode == ThemeMode.system &&
media.displayBrightness == Brightness.light)
? lightTheme
: darkTheme;

return StoreConnector<AppState, ControlsViewModel>(
converter: (store) => store.state.pageUri,
builder: (context, pageUri) {
// load custom fonts
parseFonts(control, "fonts").forEach((fontFamily, fontUrl) {
var fontUri = Uri.parse(fontUrl);
if (!fontUri.hasAuthority) {
fontUri = getAssetUri(pageUri!, fontUrl);
}
debugPrint("fontUri: $fontUri");
UserFonts.loadFont(fontFamily, fontUri);
});

return StoreConnector<AppState, PageMediaViewModel>(
distinct: true,
converter: (store) =>
ControlsViewModel.fromStore(store, childIds),
builder: (context, childrenViews) {
debugPrint("Offstage StoreConnector build");

// offstage
List<Widget> offstageWidgets = offstage != null
? childrenViews.controlViews.first.children
.where((c) =>
c.isVisible &&
c.type != ControlType.floatingActionButton)
.map((c) => createControl(offstage, c.id, disabled))
.toList()
: [];

List<Control> fab = offstage != null
? childrenViews.controlViews.first.children
.where((c) =>
c.isVisible &&
c.type == ControlType.floatingActionButton)
.toList()
: [];

var appBarView =
appBar != null ? childrenViews.controlViews.last : null;

var column = Column(
mainAxisAlignment: mainAlignment,
crossAxisAlignment: crossAlignment,
children: controls);

return MaterialApp(
title: title,
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeMode,
home: Scaffold(
appBar: appBarView != null
? AppBarControl(
parent: control,
control: appBarView.control,
children: appBarView.children,
parentDisabled: disabled,
height: appBarView.control
.attrDouble("toolbarHeight", kToolbarHeight)!,
theme: theme)
: null,
body: Stack(children: [
SizedBox.expand(
child: Container(
padding: parseEdgeInsets(control, "padding") ??
const EdgeInsets.all(10),
decoration: BoxDecoration(
color: HexColor.fromString(theme,
control.attrString("bgcolor", "")!)),
child: scrollMode != ScrollMode.none
? ScrollableControl(
child: column,
scrollDirection: Axis.vertical,
scrollMode: scrollMode,
autoScroll: autoScroll,
)
: column)),
...offstageWidgets,
const PageMedia()
]),
floatingActionButton: fab.isNotEmpty
? createControl(offstage, fab.first.id, disabled)
: null,
),
);
converter: (store) => PageMediaViewModel.fromStore(store),
builder: (context, media) {
var theme = themeMode == ThemeMode.light ||
(themeMode == ThemeMode.system &&
media.displayBrightness == Brightness.light)
? lightTheme
: darkTheme;

return StoreConnector<AppState, ControlsViewModel>(
distinct: true,
converter: (store) =>
ControlsViewModel.fromStore(store, childIds),
builder: (context, childrenViews) {
debugPrint("Offstage StoreConnector build");

// offstage
List<Widget> offstageWidgets = offstage != null
? childrenViews.controlViews.first.children
.where((c) =>
c.isVisible &&
c.type != ControlType.floatingActionButton)
.map((c) =>
createControl(offstage, c.id, disabled))
.toList()
: [];

List<Control> fab = offstage != null
? childrenViews.controlViews.first.children
.where((c) =>
c.isVisible &&
c.type == ControlType.floatingActionButton)
.toList()
: [];

var appBarView = appBar != null
? childrenViews.controlViews.last
: null;

var column = Column(
mainAxisAlignment: mainAlignment,
crossAxisAlignment: crossAlignment,
children: controls);

return MaterialApp(
title: title,
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeMode,
home: Scaffold(
appBar: appBarView != null
? AppBarControl(
parent: control,
control: appBarView.control,
children: appBarView.children,
parentDisabled: disabled,
height: appBarView.control.attrDouble(
"toolbarHeight", kToolbarHeight)!,
theme: theme)
: null,
body: Stack(children: [
SizedBox.expand(
child: Container(
padding:
parseEdgeInsets(control, "padding") ??
const EdgeInsets.all(10),
decoration: BoxDecoration(
color: HexColor.fromString(
theme,
control.attrString(
"bgcolor", "")!)),
child: scrollMode != ScrollMode.none
? ScrollableControl(
child: column,
scrollDirection: Axis.vertical,
scrollMode: scrollMode,
autoScroll: autoScroll,
)
: column)),
...offstageWidgets,
const PageMedia()
]),
floatingActionButton: fab.isNotEmpty
? createControl(offstage, fab.first.id, disabled)
: null,
),
);
});
});
});
}
Expand Down
1 change: 1 addition & 0 deletions client/lib/controls/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class TextControl extends StatelessWidget {
fontSize: control.attrDouble("size", null),
fontWeight: getFontWeight(control.attrString("weight", "")!),
fontStyle: control.attrBool("italic", false)! ? FontStyle.italic : null,
fontFamily: control.attrString("fontFamily"),
color: HexColor.fromString(
Theme.of(context), control.attrString("color", "")!),
backgroundColor: HexColor.fromString(
Expand Down
4 changes: 3 additions & 1 deletion client/lib/utils/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ ThemeData themeFromJson(Map<String, dynamic> json) {
brightness: Brightness.values.firstWhere(
(b) => b.name.toLowerCase() == json["brightness"],
orElse: () => Brightness.light),
colorSchemeSeed: HexColor.fromString(null, json["color_scheme_seed"]),
colorSchemeSeed:
HexColor.fromString(null, json["color_scheme_seed"] ?? ""),
fontFamily: json["font_family"],
useMaterial3: json["use_material3"]);
}
11 changes: 5 additions & 6 deletions client/lib/utils/uri.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ String getWebSocketEndpoint(Uri uri) {
return "$wsScheme://${uri.authority}/ws";
}

String getAssetUrl(Uri pageUri, String assetPath) {
Uri getAssetUri(Uri pageUri, String assetPath) {
return Uri(
scheme: pageUri.scheme,
host: pageUri.host,
port: pageUri.port,
path: assetPath)
.toString();
scheme: pageUri.scheme,
host: pageUri.host,
port: pageUri.port,
path: assetPath);
}
46 changes: 46 additions & 0 deletions client/lib/utils/user_fonts.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'dart:convert';

import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;

import '../models/control.dart';

class UserFonts {
static Map<String, FontLoader> fontLoaders = {};

static void loadFont(String fontFamily, Uri fontUri) {
var key = "$fontFamily$fontUri";
if (fontLoaders.containsKey(key)) {
return;
}
var fontLoader = FontLoader(fontFamily);
fontLoaders[key] = fontLoader;
fontLoader.addFont(fetchFont(fontUri));
fontLoader.load();
}

static Future<ByteData> fetchFont(Uri uri) async {
final response = await http.get(uri);

if (response.statusCode == 200) {
return ByteData.view(response.bodyBytes.buffer);
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load font $uri');
}
}
}

Map<String, String> parseFonts(Control control, String propName) {
var v = control.attrString(propName, null);
if (v == null) {
return {};
}

final j1 = json.decode(v);
return fontsFromJson(j1);
}

Map<String, String> fontsFromJson(Map<String, dynamic> json) {
return json.map((key, value) => MapEntry(key, value));
}
14 changes: 14 additions & 0 deletions client/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: "direct main"
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.4"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
image:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions client/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies:
equatable: ^2.0.3
web_socket_channel: ^2.1.0
window_manager: ^0.2.1
http: ^0.13.3

dev_dependencies:
flutter_test:
Expand Down
20 changes: 20 additions & 0 deletions client/test/utils/user_fonts_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:convert';

import 'package:flet_view/utils/user_fonts.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
test("Custom fonts are parsed from JSON", () {
const t1 = '''{
"font1": "https://fonts.com/font1.ttf",
"font2": "https://fonts.com/font2.ttf"
}''';

final j1 = json.decode(t1);
var fonts = fontsFromJson(j1);

expect(fonts.length, 2);
expect(fonts["font1"], "https://fonts.com/font1.ttf");
expect(fonts["font2"], "https://fonts.com/font2.ttf");
});
}
12 changes: 9 additions & 3 deletions sdk/python/flet/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
from flet.border import Border
from flet.border_radius import BorderRadius
from flet.constrained_control import ConstrainedControl
from flet.control import BorderStyle, Control, MarginValue, OptionalNumber, PaddingValue
from flet.control import (
BorderRadiusValue,
Control,
MarginValue,
OptionalNumber,
PaddingValue,
)
from flet.ref import Ref

try:
Expand Down Expand Up @@ -37,7 +43,7 @@ def __init__(
alignment: Alignment = None,
bgcolor: str = None,
border: Border = None,
border_radius: BorderRadius = None,
border_radius: BorderRadiusValue = None,
):
ConstrainedControl.__init__(
self,
Expand Down Expand Up @@ -134,7 +140,7 @@ def border_radius(self):

@border_radius.setter
@beartype
def border_radius(self, value: Optional[BorderRadius]):
def border_radius(self, value: BorderRadiusValue):
self.__border_radius = value
if value and isinstance(value, (int, float)):
value = border_radius.all(value)
Expand Down
3 changes: 3 additions & 0 deletions sdk/python/flet/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from beartype import beartype
from beartype.typing import List, Optional

from flet.border_radius import BorderRadius
from flet.embed_json_encoder import EmbedJsonEncoder
from flet.margin import Margin
from flet.padding import Padding
Expand Down Expand Up @@ -53,6 +54,8 @@

MarginValue = Union[None, int, float, Margin]

BorderRadiusValue = Union[None, int, float, BorderRadius]

ScrollMode = Literal[None, True, False, "none", "auto", "adaptive", "always"]


Expand Down
Loading

0 comments on commit 0d2bc15

Please sign in to comment.