Files
308ef12a35 fix(dom): fix bug for layer optimize (#3316)
* fix(dom): fix bug for layer optimize

* fix(voltron): fix dart analyze (#2)

---------

Co-authored-by: etkmao <etkmao@tecent.com>
Co-authored-by: OpenHippy <124017524+open-hippy@users.noreply.github.com>
Co-authored-by: henryjin <443836568@qq.com>
2023-06-25 02:19:21 +00:00

579 lines
15 KiB
Dart

//
// Tencent is pleased to support the open source community by making
// Hippy available.
//
// Copyright (C) 2022 THL A29 Limited, a Tencent company.
// All rights reserved.
//
// 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:provider/provider.dart';
import '../common.dart';
import '../engine.dart';
import '../render.dart';
import '../style.dart';
import '../util.dart';
import 'base.dart';
class RootWidgetViewModel extends ChangeNotifier {
static const int kRootViewTagIncrement = 100000;
final GlobalKey rootKey = GlobalKey(debugLabel: NodeProps.kRootNode);
static int sIdCounter = 0;
int _instanceId = 0;
OnResumeAndPauseListener? _onResumeAndPauseListener;
OnLoadCompleteListener? _onLoadCompleteListener;
bool _loadCompleted = false;
int _loadErrCode = 0;
bool _loadError = false;
int get id => _instanceId;
int get loadErrCode => _loadErrCode;
ContextWrapper? _wrapper;
Orientation? _orientation;
IRenderExecutor? executor;
List<IRenderExecutor> viewExecutorList = [];
late IRenderExecutor viewExecutor;
RenderContext? _context;
TimeMonitor? timeMonitor;
OnResumeAndPauseListener? onResumeAndPauseListener;
OnLoadCompleteListener? onLoadCompleteListener;
RootWidgetViewModel(int engineId) {
_instanceId = engineId * 10000 + (++sIdCounter);
viewExecutor = () {
if (viewExecutorList.isNotEmpty) {
for (var element in viewExecutorList) {
element();
}
}
};
}
bool isDebugMode = false;
Function? reload;
void restart() {
notifyListeners();
}
void attachToEngine(RenderContext context) {
_context = context;
checkUpdateDimension(-1, -1, false, false);
}
void onLoadError(int errCode) {
_loadErrCode = errCode;
_loadError = true;
_loadCompleted = true;
notifyChange();
}
BuildContext? get currentContext => _wrapper?.call();
bool get loadError => _loadError;
bool get loadFinish => _loadCompleted;
RenderTree? get renderTree => _context?.renderManager.controllerManager.findTree(id);
void onResume() {
_onResumeAndPauseListener?.onInstanceResume(id);
}
void onPause() {
_onResumeAndPauseListener?.onInstancePause(id);
}
void destroy() {
_wrapper = null;
}
void notifyChange() {
notifyListeners();
WidgetsBinding.instance.scheduleFrame();
}
// RootView上添加View了,说明jsBundle正常工作了
void onViewAdd() {
if (!_loadCompleted) {
_loadCompleted = true;
notifyChange();
var _timeMonitor = timeMonitor;
if (_timeMonitor != null) {
_timeMonitor.end();
var onLoadCompleteListener = _onLoadCompleteListener;
if (onLoadCompleteListener != null) {
onLoadCompleteListener(
_timeMonitor.totalTime,
_timeMonitor.events,
);
}
_context?.engineMonitor.reportModuleLoadComplete(
this,
_timeMonitor.totalTime,
_timeMonitor.events,
);
}
}
}
void updateRootSize() {
var rootSize = Size.zero;
rootSize = getSizeFromKey(rootKey);
_context?.renderBridgeManager
.updateNodeSize(id, width: rootSize.width, height: rootSize.height);
}
void onGlobalLayout() {
var uiContext = currentContext;
if (_loadCompleted) {
if (uiContext != null) {
var curOrientation = ScreenUtil.getOrientation(uiContext);
if (curOrientation != _orientation) {
_orientation = curOrientation;
checkUpdateDimension(-1, -1, false, false);
}
}
}
}
void checkUpdateDimension(
int windowWidth,
int windowHeight,
bool shouldUseScreenDisplay,
bool systemUiVisibilityChanged,
) {
var uiContext = currentContext;
if (_context == null && uiContext != null) {
return;
}
if (uiContext != null) {
var dimensionMap = getDimensions(
windowWidth,
windowHeight,
shouldUseScreenDisplay,
uiContext,
);
_context?.dimensionChecker.checkUpdateDimension(
uiContext,
dimensionMap,
windowWidth,
windowHeight,
shouldUseScreenDisplay,
systemUiVisibilityChanged,
);
}
}
}
class VoltronWidget extends StatefulWidget {
// 页面加载过程中的loading widget
final ModuleLoadingBuilder? loadingBuilder;
// 页面加载错误widget
final ModuleErrorBuilder? errorBuilder;
// 空页面展示widget
final EmptyBuilder? emptyBuilder;
// 是否支持可变高度,为false时,在仅高度变化,宽不变的场景下,父布局size保证第一次测量高度
// 设置为true的情况下,会导致input组件无法自动滚动
final bool resizedHeight;
// 固定高度模式,height > 0的情况下,会使用固定高度,即高度不跟随外面布局变化
final double height;
final RendererLoader loader;
const VoltronWidget({
Key? key,
required this.loader,
this.loadingBuilder,
this.errorBuilder,
this.emptyBuilder,
this.resizedHeight = false,
this.height = -1,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _VoltronWidgetState(loader.engineId);
}
}
class _VoltronWidgetState extends State<VoltronWidget>
with
TickerProviderStateMixin,
// ignore: prefer_mixin
WidgetsBindingObserver {
Size? oldSize;
late RootWidgetViewModel viewModel;
Orientation? _orientation;
double? _curHeight;
double? _curWidth;
bool hasDispose = false;
Offset? debugButtonOffset;
_VoltronWidgetState(int engineId) {
viewModel = RootWidgetViewModel(engineId);
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback(doFirstFrame);
viewModel._wrapper = () => context;
hasDispose = false;
}
@override
void didUpdateWidget(VoltronWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.loader != oldWidget.loader) {
WidgetsBinding.instance.addPostFrameCallback(doFirstFrame);
}
}
@override
Widget build(BuildContext context) {
LogUtils.dWidget("ID:${viewModel.id}, build root widget");
return LayoutBuilder(builder: (context, constraints) {
if (hasDispose) {
return _empty(context);
}
if (widget.resizedHeight) {
// 高度自适应外层布局模式
// 此模式下,会出现键盘弹起后,输入框不跟随键盘顶起的问题
// 即输入框会被键盘遮挡
return _contentWithHeight(constraints.maxHeight);
} else {
if (widget.height > 0) {
// 固定高度模式
// 布局高度直接由外部的高度决定
return _contentWithHeight(widget.height);
} else {
// 高度部分自适应模式
// 仅当宽高都发生变化,或者屏幕旋转后,才修改高度
var newHeight = constraints.maxHeight;
var newWidth = constraints.maxWidth;
var newOrientation = ScreenUtil.getOrientation(context);
if (_orientation == null || _curWidth == null || _curHeight == null) {
_curWidth = newWidth;
_curHeight = newHeight;
_orientation = newOrientation;
} else {
if (_curWidth != newWidth || _orientation != newOrientation) {
// 当宽度不等或者屏幕方向发生改变时,直接重设高度值
_curWidth = newWidth;
_curHeight = newHeight;
_orientation = newOrientation;
}
}
return _contentWithHeight(_curHeight!);
}
}
});
}
Widget _contentWithHeight(double height) {
return ChangeNotifierProvider.value(
value: viewModel,
child: _contentWithHeightByRepaint(height),
);
}
Widget _contentWithHeightByRepaint(double height) {
// 需要使用RepaintBoundary包裹,以便获取当前的页面快照
return RepaintBoundary(
key: viewModel.rootKey,
child: SizedBox(
width: double.infinity,
height: height,
child: _contentWithStatus(),
),
);
}
Widget _contentWithStatus() {
return Consumer<RootWidgetViewModel>(
builder: (context, viewModel, widget) {
var model = LoadingModel(!(viewModel.loadFinish), viewModel.loadError);
if (model.isLoading) {
LogUtils.dWidget("ID:${viewModel.id}, build root widget, build content loading");
return _loading(context);
} else if (model.isError) {
LogUtils.dWidget("ID:${viewModel.id}, build root widget, build content error");
return _error(context, viewModel);
} else {
LogUtils.dWidget("ID:${viewModel.id}, build root widget, build content");
return _content(viewModel);
}
},
);
}
Widget _loading(BuildContext context) {
var loadingBuilder = widget.loadingBuilder;
if (loadingBuilder != null) {
return loadingBuilder(context);
}
return Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
CircularProgressIndicator(),
Text("Loading"),
],
),
);
}
Widget _error(BuildContext context, RootWidgetViewModel viewModel) {
var errorBuilder = widget.errorBuilder;
int errCode = viewModel.loadErrCode;
if (errorBuilder != null) {
return errorBuilder(context, errCode);
}
return Center(
child: Text("Load error, code: ${errCode.toString()}"),
);
}
Widget _empty(BuildContext context) {
var emptyBuilder = widget.loadingBuilder;
if (emptyBuilder != null) {
return emptyBuilder(context);
}
return Container();
}
Widget _content(RootWidgetViewModel viewModel) {
var nodeList = <RenderNode>[];
var tree = viewModel.renderTree;
if (tree != null) {
var rootNode = tree.rootNode;
if (rootNode != null && rootNode.childCount > 0) {
nodeList.addAll(rootNode.children);
}
}
if (nodeList.isNotEmpty) {
return _container(context, nodeList);
} else {
return _empty(context);
}
}
Widget _container(BuildContext context, List<RenderNode> childList) {
var childrenWidget = <Widget>[];
for (var element in childList) {
childrenWidget.add(_generateByRenderNode(context, element));
}
if (viewModel.isDebugMode) {
childrenWidget.add(_reloadWidget(context));
}
return Stack(children: childrenWidget);
}
Widget _reloadWidget(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final size = mediaQuery.size;
final height = size.height;
final safeTop = mediaQuery.padding.top;
final currentOffset = debugButtonOffset;
var dx = 20.0;
var dy = safeTop + 40;
if (currentOffset != null) {
dx = currentOffset.dx;
dy = currentOffset.dy;
}
return Positioned(
left: dx,
top: dy,
child: Draggable(
child: PopupMenuButton(
itemBuilder: (BuildContext context) {
return [
PopupMenuItem<int>(
value: 0,
child: const Text('reload'),
onTap: () {
var reloadFn = viewModel.reload;
if (reloadFn != null) {
reloadFn();
}
},
),
];
},
child: _buildReloadButton(),
),
//拖动过程中的Widget
feedback: _buildReloadButton(),
childWhenDragging: Container(),
//拖动结束后的Widget
onDraggableCanceled: (velocity, offset) {
// 计算组件可移动范围 更新位置信息
if (!mounted) return;
setState(
() {
var x = offset.dx;
var y = offset.dy;
if (offset.dx < 0) {
x = 20;
}
if (offset.dx + 40 > 375) {
x = 335;
}
if (offset.dy < kBottomNavigationBarHeight) {
y = kBottomNavigationBarHeight;
}
if (offset.dy > height - 100) {
y = height - 100;
}
debugButtonOffset = Offset(x, y);
},
);
},
),
);
}
Widget _buildReloadButton() {
return const Icon(
Icons.logo_dev,
color: Color(0xFF333333),
size: 40,
);
}
Widget _generateByRenderNode(BuildContext context, RenderNode childNode) {
return childNode.findController().createWidget(context, childNode.renderViewModel);
}
void _loadModule() {
widget.loader.load(viewModel);
}
void doFirstFrame(Duration timeStamp) {
/// 防止从引擎缓存时启动,宽高都为0的情况
var rootSize = getSizeFromKey(viewModel.rootKey);
if (rootSize.width > 0 && rootSize.height > 0) {
_loadModule();
} else {
WidgetsBinding.instance.addPostFrameCallback(doFirstFrame);
}
}
void didChangeMetrics() {
if (!hasDispose) {
viewModel.onGlobalLayout();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final RenderBox? renderBox =
viewModel.rootKey.currentContext?.findRenderObject() as RenderBox;
var newSize = renderBox?.size;
if (newSize != null) {
var originOldSize = oldSize;
if (originOldSize == null ||
originOldSize.width != newSize.width ||
originOldSize.height != newSize.height) {
viewModel.updateRootSize();
oldSize = Size(
newSize.width,
newSize.height,
);
}
}
});
}
}
@override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
viewModel.dispose();
hasDispose = true;
}
}
typedef EmptyBuilder = Widget Function(
BuildContext context,
);
typedef ModuleLoadingBuilder = Widget Function(
BuildContext context,
);
typedef ModuleErrorBuilder = Widget Function(
BuildContext context,
int statusCode,
);
typedef OnLoadCompleteListener = Function(int loadTime, List<EngineMonitorEvent> loadEvents);
abstract class OnResumeAndPauseListener {
void onInstanceResume(int id);
void onInstancePause(int id);
}
abstract class GlobalLayoutListener {
void onSystemUiVisibilityChange(int visibility);
void onGlobalLayout();
}
class LoadingModel {
final bool isLoading;
final bool isError;
const LoadingModel(this.isLoading, this.isError);
@override
bool operator ==(Object other) =>
other is LoadingModel && other.isLoading == isLoading && other.isError == isError;
@override
int get hashCode => isLoading.hashCode | isError.hashCode;
}