Skip to content

Custom fonts #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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