huawei_ar 3.8.0+300
huawei_ar: ^3.8.0+300 copied to clipboard
Huawei AR Engine Flutter Plugin is a platform for building augmented reality (AR) apps on Android smartphones using the Flutter framework.
/*
Copyright 2020-2026. Huawei Technologies Co., Ltd. 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
https://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:huawei_ar/huawei_ar.dart';
import 'package:huawei_ar_example/ar_body_scene.dart';
import 'package:huawei_ar_example/ar_face_scene.dart';
import 'package:huawei_ar_example/ar_hand_scene.dart';
import 'package:huawei_ar_example/ar_world_scene.dart';
import 'package:huawei_ar_example/ar_augmented_image_scene.dart';
import 'package:huawei_ar_example/ar_face_health_scene.dart';
import 'package:huawei_ar_example/ar_scene_mesh_scene.dart';
import 'package:huawei_ar_example/ar_world_body_scene.dart';
/// -----------------------------------------------------------------------------
/// Huawei AR Engine Flutter demo – application entry point.
/// -----------------------------------------------------------------------------
///
/// This file hosts the top level application widget for the Huawei AR Engine
/// Flutter example. The demo presents a simple menu that allows the different
/// AR scene examples that ship with the plugin to be launched individually.
///
/// The menu is intentionally kept simple: it consists of a status indicator –
/// which reports whether the AR Engine service APK is available on the device –
/// followed by a list of buttons, one for each of the bundled example scenes.
///
/// The individual scene widgets live in their own files and are imported above.
/// Each button simply pushes the corresponding scene onto the navigation stack.
///
/// None of the helpers in this file change the behaviour of the AR Engine. They
/// only describe how the menu is laid out and how the scenes are launched.
/// -----------------------------------------------------------------------------
/// The entry point of the application.
///
/// This function is intentionally kept as small as possible: it only starts the
/// Flutter framework with the top level [MyApp] widget.
void main() {
runApp(
const MyApp(),
);
}
/// Collection of constant values that describe the visual appearance of the
/// demo menu.
///
/// Keeping those values in a single, dedicated location makes it easier to see
/// – at a glance – which colors, sizes and captions are being used by the menu.
/// The values are intentionally identical to the ones that were previously
/// written inline throughout the widget tree.
class _MenuConstants {
const _MenuConstants._();
/// The title displayed in the application bar of the menu.
static const String appBarTitle = 'Huawei AREngine Flutter Demo';
/// The background color of the application bar of the menu.
static const Color appBarColor = Colors.red;
/// The caption of the status indicator that reports the APK readiness.
static const String statusLabel = 'AREngine Service APK Ready';
/// The caption displayed when the AR Engine service APK is available.
static const String statusReadyText = 'Yes';
/// The caption displayed when the AR Engine service APK is not available.
static const String statusNotReadyText = 'No';
/// The color used for the status indicator before the check has completed.
static const Color statusPendingColor = Colors.grey;
/// The color used for the status indicator when the APK is available.
static const Color statusReadyColor = Colors.green;
/// The color used for the status indicator when the APK is not available.
static const Color statusNotReadyColor = Colors.red;
/// The caption of the button that opens the AppGallery page.
static const String appGalleryButtonText = 'Navigate To AppGallery Page';
/// The height of the spacer placed above the status indicator row.
static const double topSpacerHeight = 10;
/// The default color used for the example scene buttons.
static const Color buttonColor = Colors.red;
/// The color used for the button that opens the AppGallery page.
static const Color appGalleryButtonColor = Colors.black;
/// The color used for the foreground content of the buttons.
static const Color buttonForegroundColor = Colors.white;
/// The width of the spacer placed between a button icon and its label.
static const double buttonIconSpacing = 10;
/// The uniform padding applied around each example scene button.
static const EdgeInsets buttonPadding = EdgeInsets.all(5.0);
/// The height of the status indicator containers.
static const double statusContainerHeight = 50;
/// The uniform padding applied inside the status indicator containers.
static const EdgeInsets statusContainerPadding = EdgeInsets.all(5.0);
/// The width of the border drawn around the status indicator containers.
static const double statusBorderWidth = 2.0;
/// The corner radius applied to the status indicator containers.
static const double statusBorderRadius = 5.0;
/// The font size used for the text inside the status indicator containers.
static const double statusFontSize = 16;
}
/// A lightweight, immutable description of a single example scene entry.
///
/// Each entry bundles together the caption and icon that should be displayed on
/// the corresponding menu button together with a [builder] that creates the
/// scene widget when the button is pressed. Modelling the menu entries as data
/// keeps the widget tree in [build] short and makes it trivial to add, remove
/// or reorder the example scenes.
class _SceneMenuEntry {
/// Creates a description of a single example scene menu entry.
const _SceneMenuEntry({
required this.label,
required this.icon,
required this.builder,
});
/// The caption displayed on the menu button.
final String label;
/// The icon displayed on the menu button.
final IconData icon;
/// A builder that creates the scene widget for this entry.
final WidgetBuilder builder;
}
/// The top level application widget for the Huawei AR Engine Flutter demo.
///
/// The widget itself does not hold any mutable state. All of the mutable state
/// lives inside the associated [State] object, [_MyAppState].
class MyApp extends StatefulWidget {
/// Creates the top level [MyApp] widget.
///
/// The [key] argument is forwarded, unchanged, to the [StatefulWidget]
/// super constructor.
const MyApp({
Key? key,
}) : super(
key: key,
);
@override
State<MyApp> createState() => _MyAppState();
}
/// The [State] implementation that backs the [MyApp] widget.
///
/// This object keeps track of whether the AR Engine service APK is ready and
/// updates the color of the status indicator accordingly.
class _MyAppState extends State<MyApp> {
/// Whether or not the AR Engine service APK has been reported as ready.
bool _isAREngineAPKReady = false;
/// The color currently used for the status indicator.
///
/// The color starts out as a neutral pending color and is updated once the
/// asynchronous readiness check has completed.
Color _serviceAppCheckColor = _MenuConstants.statusPendingColor;
@override
void initState() {
super.initState();
// Kick off the asynchronous readiness check as soon as the state is ready.
_checkServiceApk();
}
/// Asynchronously checks whether the AR Engine service APK is available.
///
/// The result is stored in the state and drives both the caption and the
/// color of the status indicator. The method guards against updating a widget
/// that is no longer mounted.
void _checkServiceApk() async {
if (!mounted) return;
bool result = await AREngine.isArEngineServiceApkReady();
setState(() {
_isAREngineAPKReady = result;
_serviceAppCheckColor = result
? _MenuConstants.statusReadyColor
: _MenuConstants.statusNotReadyColor;
});
}
/// Pushes the scene created by [builder] onto the navigation stack.
///
/// Centralising the navigation logic here keeps the individual menu entries
/// concise and guarantees that every scene is launched in exactly the same
/// way.
void _openScene(BuildContext context, WidgetBuilder builder) {
Navigator.of(context).push(
MaterialPageRoute<dynamic>(
builder: builder,
),
);
}
/// Builds the list of example scene menu entries.
///
/// The list is intentionally described as data so that the widget tree in
/// [build] can be generated from it. The order of the entries matches the
/// order in which the buttons were previously declared inline.
List<_SceneMenuEntry> _sceneMenuEntries() {
return <_SceneMenuEntry>[
_SceneMenuEntry(
label: 'ARFace Scene',
icon: Icons.face,
builder: (BuildContext context) {
return const ArFaceScreen();
},
),
_SceneMenuEntry(
label: 'ARHand Scene',
icon: Icons.pan_tool,
builder: (BuildContext context) {
return const ArHandScene();
},
),
_SceneMenuEntry(
label: 'ARBody Scene',
icon: Icons.accessibility,
builder: (BuildContext context) {
return const ArBodyScene();
},
),
_SceneMenuEntry(
label: 'ARWorld Scene',
icon: Icons.public,
builder: (BuildContext context) {
return const ARWorldScene();
},
),
_SceneMenuEntry(
label: 'ARAugmentedImage Scene',
icon: Icons.image,
builder: (BuildContext context) {
return const ArAugmentedImageScene();
},
),
_SceneMenuEntry(
label: 'ARWorldBody Scene',
icon: Icons.accessibility,
builder: (BuildContext context) {
return const ArWorldBodyScene();
},
),
_SceneMenuEntry(
label: 'FaceHealth Scene',
icon: Icons.health_and_safety,
builder: (BuildContext context) {
return const ArFaceHealthScreen();
},
),
_SceneMenuEntry(
label: 'ARSceneMesh Scene',
icon: Icons.grid_on,
builder: (BuildContext context) {
return const ArSceneMeshScene();
},
),
];
}
/// Builds the row that reports whether the AR Engine service APK is ready.
///
/// The row consists of two containers: a wider one that displays the static
/// caption and a narrower one that displays the dynamic yes/no answer.
Widget _buildStatusRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_customContainer(
_serviceAppCheckColor,
_MenuConstants.statusLabel,
5,
false,
),
_customContainer(
_serviceAppCheckColor,
_isAREngineAPKReady
? _MenuConstants.statusReadyText
: _MenuConstants.statusNotReadyText,
1,
true,
),
],
);
}
/// Builds the button that opens the Huawei AppGallery page.
Widget _buildAppGalleryButton() {
return _expandedButton(
() => AREngine.navigateToAppMarketPage(),
_MenuConstants.appGalleryButtonText,
Icons.store,
color: _MenuConstants.appGalleryButtonColor,
);
}
/// Builds the list of buttons – one for each of the bundled example scenes.
///
/// The buttons are generated from the data returned by [_sceneMenuEntries] so
/// that the widget tree stays in lock-step with the declared menu entries.
List<Widget> _buildSceneButtons(BuildContext context) {
return _sceneMenuEntries().map<Widget>((_SceneMenuEntry entry) {
return _expandedButton(
() => _openScene(context, entry.builder),
entry.label,
entry.icon,
);
}).toList();
}
/// Builds the complete list of children that make up the menu body.
///
/// The children consist of a spacer, the status row, the AppGallery button
/// and finally the list of example scene buttons.
List<Widget> _buildMenuChildren(BuildContext context) {
return <Widget>[
const SizedBox(
height: _MenuConstants.topSpacerHeight,
),
_buildStatusRow(),
_buildAppGalleryButton(),
..._buildSceneButtons(context),
];
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(_MenuConstants.appBarTitle),
centerTitle: true,
backgroundColor: _MenuConstants.appBarColor,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: _buildMenuChildren(context),
),
),
);
},
),
);
}
/// Builds a single, flexible menu button.
///
/// The button displays [iconData] followed by [buttonText] and invokes
/// [onPressed] when tapped. When [color] is omitted a default color is used.
Widget _expandedButton(
Function()? onPressed,
String buttonText,
IconData iconData, {
Color? color,
}) {
return Flexible(
flex: 2,
child: SizedBox(
child: Padding(
padding: _MenuConstants.buttonPadding,
child: MaterialButton(
onPressed: onPressed,
color: color ?? _MenuConstants.buttonColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
iconData,
color: _MenuConstants.buttonForegroundColor,
),
const SizedBox(
width: _MenuConstants.buttonIconSpacing,
),
Text(
buttonText,
style: const TextStyle(
color: _MenuConstants.buttonForegroundColor,
),
),
],
),
),
),
),
);
}
/// Builds one of the two status indicator containers.
///
/// The [borderColor] argument controls the color of the border, [text] is the
/// caption shown inside the container, [flex] controls how much horizontal
/// space the container occupies and [reverseBorder] selects which side of the
/// container is rounded and where the outer margin is applied.
Widget _customContainer(
Color borderColor,
String text,
int flex,
bool reverseBorder,
) {
if (reverseBorder) {
return Flexible(
flex: flex,
child: Container(
height: _MenuConstants.statusContainerHeight,
padding: _MenuConstants.statusContainerPadding,
margin: const EdgeInsets.only(right: 5.0),
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(_MenuConstants.statusBorderRadius),
bottomRight: Radius.circular(_MenuConstants.statusBorderRadius),
),
border: Border.all(
color: _serviceAppCheckColor,
width: _MenuConstants.statusBorderWidth,
),
),
child: Center(
child: Text(
text,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: _MenuConstants.statusFontSize,
),
),
),
),
);
}
return Flexible(
flex: flex,
child: Container(
height: _MenuConstants.statusContainerHeight,
padding: _MenuConstants.statusContainerPadding,
margin: const EdgeInsets.only(left: 5.0),
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(_MenuConstants.statusBorderRadius),
bottomLeft: Radius.circular(_MenuConstants.statusBorderRadius),
),
border: Border.all(
color: _serviceAppCheckColor,
width: _MenuConstants.statusBorderWidth,
),
),
child: Center(
child: Text(
text,
style: const TextStyle(
fontSize: _MenuConstants.statusFontSize,
),
),
),
),
);
}
}