1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components_ng/pattern/navigation/navdestination_pattern_base.h"
17 
18 #include "core/components_ng/pattern/navigation/navdestination_node_base.h"
19 #include "core/components_ng/pattern/navigation/navigation_title_util.h"
20 
21 namespace OHOS::Ace::NG {
22 namespace {
23 constexpr int32_t DEFAULT_ANIMATION_DURATION = 500;
24 } // namespace
25 
SetTitleBarStyle(const std::optional<BarStyle> & barStyle)26 void NavDestinationPatternBase::SetTitleBarStyle(const std::optional<BarStyle>& barStyle)
27 {
28     if (titleBarStyle_ != barStyle) {
29         // Mark need update safeAreaPadding when it is enabled or disabled.
30         if (barStyle.value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING ||
31             titleBarStyle_.value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING) {
32             safeAreaPaddingChanged_ = true;
33         }
34         titleBarStyle_ = barStyle;
35         auto host = GetHost();
36         CHECK_NULL_VOID(host);
37         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
38     }
39 }
40 
SetToolBarStyle(const std::optional<BarStyle> & barStyle)41 void NavDestinationPatternBase::SetToolBarStyle(const std::optional<BarStyle>& barStyle)
42 {
43     if (toolBarStyle_ != barStyle) {
44         // Mark need update safeAreaPadding when it is enabled or disabled.
45         if (barStyle.value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING ||
46             toolBarStyle_.value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING) {
47             safeAreaPaddingChanged_ = true;
48         }
49         toolBarStyle_ = barStyle;
50         auto host = GetHost();
51         CHECK_NULL_VOID(host);
52         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
53     }
54 }
55 
UpdateLayoutPropertyBeforeAnimation(const RefPtr<NavDestinationNodeBase> & navNodeBase,bool needRunTitleBarAnimation,bool needRunToolBarAnimation,bool hideTitleBar,bool hideToolBar)56 void NavDestinationPatternBase::UpdateLayoutPropertyBeforeAnimation(const RefPtr<NavDestinationNodeBase>& navNodeBase,
57     bool needRunTitleBarAnimation, bool needRunToolBarAnimation, bool hideTitleBar, bool hideToolBar)
58 {
59     CHECK_NULL_VOID(navNodeBase);
60     auto property = navNodeBase->GetLayoutProperty<NavDestinationLayoutPropertyBase>();
61     CHECK_NULL_VOID(property);
62     if (needRunTitleBarAnimation && titleBarAnimationCount_ == 0) {
63         property->UpdateTitleBarTranslateState(hideTitleBar ?
64             BarTranslateState::TRANSLATE_ZERO : BarTranslateState::TRANSLATE_HEIGHT);
65         if (!hideTitleBar) {
66             UpdateTitleBarProperty(property, false, navNodeBase);
67         }
68     }
69     if (needRunToolBarAnimation && toolBarAnimationCount_ == 0) {
70         property->UpdateToolBarTranslateState(hideToolBar ?
71             BarTranslateState::TRANSLATE_ZERO : BarTranslateState::TRANSLATE_HEIGHT);
72         if (!hideToolBar) {
73             UpdateToolBarAndDividerProperty(property, false, navNodeBase);
74         }
75     }
76 }
77 
HandleTitleBarAndToolBarAnimation(const RefPtr<NavDestinationNodeBase> & navNodeBase,bool needRunTitleBarAnimation,bool needRunToolBarAnimation)78 void NavDestinationPatternBase::HandleTitleBarAndToolBarAnimation(const RefPtr<NavDestinationNodeBase>& navNodeBase,
79     bool needRunTitleBarAnimation, bool needRunToolBarAnimation)
80 {
81     if (!(needRunToolBarAnimation || needRunTitleBarAnimation)) {
82         return;
83     }
84 
85     CHECK_NULL_VOID(navNodeBase);
86     auto pipeline = navNodeBase->GetContext();
87     CHECK_NULL_VOID(pipeline);
88     auto property = navNodeBase->GetLayoutProperty<NavDestinationLayoutPropertyBase>();
89     CHECK_NULL_VOID(property);
90     bool hideTitleBar = property->GetHideTitleBarValue(false);
91     bool hideToolBar = property->GetHideToolBarValue(false);
92     UpdateLayoutPropertyBeforeAnimation(navNodeBase, needRunTitleBarAnimation,
93         needRunToolBarAnimation, hideTitleBar, hideToolBar);
94 
95     auto task = [weakPattern = WeakClaim(this), needRunTitleBarAnimation, needRunToolBarAnimation,
96         hideTitleBar, hideToolBar]() mutable {
97         auto pattern = weakPattern.Upgrade();
98         CHECK_NULL_VOID(pattern);
99         auto node = AceType::DynamicCast<NavDestinationNodeBase>(pattern->GetHost());
100         CHECK_NULL_VOID(node);
101         auto property = node->GetLayoutProperty<NavDestinationLayoutPropertyBase>();
102         CHECK_NULL_VOID(property);
103         if (pattern->IsNeedHideToolBarForNavWidth()) {
104             property->ResetToolBarTranslateState();
105             needRunToolBarAnimation = false;
106         }
107         if (!(needRunToolBarAnimation || needRunTitleBarAnimation)) {
108             return;
109         }
110 
111         auto titleBarNode = AceType::DynamicCast<TitleBarNode>(node->GetTitleBarNode());
112         if (needRunTitleBarAnimation && !hideTitleBar && titleBarNode && pattern->GetTitleBarAnimationCount() == 0) {
113             pattern->UpdateTitleBarTranslateAndOpacity(true, titleBarNode, pattern->GetTitleBarHeight());
114         }
115         auto toolBarNode = AceType::DynamicCast<NavToolbarNode>(node->GetToolBarNode());
116         auto toolBarDividerNode = AceType::DynamicCast<FrameNode>(node->GetToolBarDividerNode());
117         if (needRunToolBarAnimation && !hideToolBar && toolBarNode && pattern->GetToolBarAnimationCount() == 0) {
118             pattern->UpdateToolBarAndDividerTranslateAndOpacity(true, toolBarNode,
119                 pattern->GetToolBarHeight(), toolBarDividerNode, pattern->GetToolBarDividerHeight());
120         }
121 
122         pattern->StartAnimation(needRunTitleBarAnimation, hideTitleBar, needRunToolBarAnimation, hideToolBar);
123     };
124     pipeline->AddAfterLayoutTask(std::move(task));
125 }
126 
UpdateTitleBarProperty(const RefPtr<LayoutProperty> & navBarLayoutProperty,bool hide,const RefPtr<NavDestinationNodeBase> & hostNode)127 void NavDestinationPatternBase::UpdateTitleBarProperty(const RefPtr<LayoutProperty>& navBarLayoutProperty, bool hide,
128     const RefPtr<NavDestinationNodeBase>& hostNode)
129 {
130     auto titleBarNode = AceType::DynamicCast<TitleBarNode>(hostNode->GetTitleBarNode());
131     CHECK_NULL_VOID(titleBarNode);
132     auto titleBarLayoutProperty = titleBarNode->GetLayoutProperty();
133     CHECK_NULL_VOID(titleBarLayoutProperty);
134     if (hide) {
135         titleBarLayoutProperty->UpdateVisibility(VisibleType::GONE);
136         titleBarNode->SetJSViewActive(false);
137     } else {
138         titleBarLayoutProperty->UpdateVisibility(VisibleType::VISIBLE);
139         titleBarNode->SetJSViewActive(true);
140         auto&& opts = navBarLayoutProperty->GetSafeAreaExpandOpts();
141         if (opts) {
142             titleBarLayoutProperty->UpdateSafeAreaExpandOpts(*opts);
143         }
144     }
145 }
146 
UpdateTitleBarTranslateAndOpacity(bool hide,const RefPtr<TitleBarNode> & titleBarNode,float titleBarHeight)147 void NavDestinationPatternBase::UpdateTitleBarTranslateAndOpacity(
148     bool hide, const RefPtr<TitleBarNode>& titleBarNode, float titleBarHeight)
149 {
150     if (titleBarNode) {
151         auto renderContext = titleBarNode->GetRenderContext();
152         if (renderContext) {
153             auto offset = renderContext->GetTranslateXYProperty();
154             offset.SetY(hide ? -titleBarHeight : 0.0f);
155             renderContext->UpdateTranslateInXY(offset);
156             renderContext->UpdateOpacity(hide ? 0.0f : 1.0f);
157         }
158     }
159 }
160 
UpdateToolBarAndDividerProperty(const RefPtr<LayoutProperty> & navBarLayoutProperty,bool hide,const RefPtr<NavDestinationNodeBase> & hostNode)161 void NavDestinationPatternBase::UpdateToolBarAndDividerProperty(const RefPtr<LayoutProperty>& navBarLayoutProperty,
162     bool hide, const RefPtr<NavDestinationNodeBase>& hostNode)
163 {
164     auto toolBarNode = AceType::DynamicCast<NavToolbarNode>(hostNode->GetToolBarNode());
165     CHECK_NULL_VOID(toolBarNode);
166     auto toolBarLayoutProperty = toolBarNode->GetLayoutProperty();
167     CHECK_NULL_VOID(toolBarLayoutProperty);
168     auto toolBarDividerNode = AceType::DynamicCast<FrameNode>(hostNode->GetToolBarDividerNode());
169     RefPtr<LayoutProperty> toolBarDividerLayoutProperty = nullptr;
170     if (toolBarDividerNode) {
171         toolBarDividerLayoutProperty = toolBarDividerNode->GetLayoutProperty();
172     }
173     if (hide || !toolBarNode->HasValidContent()) {
174         toolBarLayoutProperty->UpdateVisibility(VisibleType::GONE);
175         toolBarNode->SetActive(false);
176         if (toolBarDividerLayoutProperty) {
177             toolBarDividerLayoutProperty->UpdateVisibility(VisibleType::GONE);
178         }
179     } else {
180         toolBarLayoutProperty->UpdateVisibility(VisibleType::VISIBLE);
181         toolBarNode->SetActive(true);
182         if (toolBarDividerLayoutProperty) {
183             toolBarDividerLayoutProperty->UpdateVisibility(VisibleType::VISIBLE);
184         }
185 
186         auto&& opts = navBarLayoutProperty->GetSafeAreaExpandOpts();
187         if (opts) {
188             toolBarLayoutProperty->UpdateSafeAreaExpandOpts(*opts);
189         }
190     }
191 }
192 
UpdateToolBarAndDividerTranslateAndOpacity(bool hide,const RefPtr<NavToolbarNode> & toolBarNode,float toolBarHeight,const RefPtr<FrameNode> & toolbarDividerNode,float toolBarDividerHeight)193 void NavDestinationPatternBase::UpdateToolBarAndDividerTranslateAndOpacity(bool hide,
194     const RefPtr<NavToolbarNode>& toolBarNode, float toolBarHeight, const RefPtr<FrameNode>& toolbarDividerNode,
195     float toolBarDividerHeight)
196 {
197     float opacity = hide ? 0.0f : 1.0f;
198     float offsetY = hide ? (toolBarHeight + toolBarDividerHeight) : 0;
199     if (toolBarNode) {
200         auto renderContext = toolBarNode->GetRenderContext();
201         if (renderContext) {
202             renderContext->UpdateTranslateInXY({ 0.0f, offsetY });
203             renderContext->UpdateOpacity(opacity);
204         }
205     }
206     if (toolbarDividerNode) {
207         auto dividerContext = toolbarDividerNode->GetRenderContext();
208         if (dividerContext) {
209             dividerContext->UpdateTranslateInXY({ 0.0f, offsetY });
210             dividerContext->UpdateOpacity(opacity);
211         }
212     }
213 }
214 
HideOrShowTitleBarImmediately(const RefPtr<NavDestinationNodeBase> & hostNode,bool hide)215 void NavDestinationPatternBase::HideOrShowTitleBarImmediately(const RefPtr<NavDestinationNodeBase>& hostNode, bool hide)
216 {
217     auto navBarPattern = hostNode->GetPattern<NavDestinationPatternBase>();
218     CHECK_NULL_VOID(navBarPattern);
219     auto navBarLayoutProperty = hostNode->GetLayoutProperty();
220     CHECK_NULL_VOID(navBarLayoutProperty);
221     auto titleBarNode = AceType::DynamicCast<TitleBarNode>(hostNode->GetTitleBarNode());
222     CHECK_NULL_VOID(titleBarNode);
223     UpdateTitleBarProperty(navBarLayoutProperty, hide, hostNode);
224     UpdateTitleBarTranslateAndOpacity(hide, titleBarNode, navBarPattern->GetTitleBarHeight());
225     titleBarNode->MarkModifyDone();
226     titleBarNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
227 }
228 
HideOrShowToolBarImmediately(const RefPtr<NavDestinationNodeBase> & hostNode,bool hide)229 void NavDestinationPatternBase::HideOrShowToolBarImmediately(const RefPtr<NavDestinationNodeBase>& hostNode, bool hide)
230 {
231     auto navDestinationPatternBase = hostNode->GetPattern<NavDestinationPatternBase>();
232     CHECK_NULL_VOID(navDestinationPatternBase);
233     auto navBarLayoutProperty = hostNode->GetLayoutProperty();
234     CHECK_NULL_VOID(navBarLayoutProperty);
235     auto toolBarNode = AceType::DynamicCast<NavToolbarNode>(hostNode->GetToolBarNode());
236     CHECK_NULL_VOID(toolBarNode);
237     auto toolBarDividerNode = AceType::DynamicCast<FrameNode>(hostNode->GetToolBarDividerNode());
238     UpdateToolBarAndDividerProperty(navBarLayoutProperty, hide, hostNode);
239     UpdateToolBarAndDividerTranslateAndOpacity(hide, toolBarNode, GetToolBarHeight(),
240         toolBarDividerNode, GetToolBarDividerHeight());
241     toolBarNode->MarkModifyDone();
242     toolBarNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
243 }
244 
BarAnimationPropertyCallback(bool needRunTitleBarAnimation,bool hideTitle,bool needRunToolBarAnimation,bool hideTool)245 void NavDestinationPatternBase::BarAnimationPropertyCallback(
246     bool needRunTitleBarAnimation, bool hideTitle, bool needRunToolBarAnimation, bool hideTool)
247 {
248     auto node = AceType::DynamicCast<NavDestinationNodeBase>(GetHost());
249     CHECK_NULL_VOID(node);
250     auto property = node->GetLayoutProperty<NavDestinationLayoutPropertyBase>();
251     CHECK_NULL_VOID(property);
252     auto context = node->GetContext();
253     CHECK_NULL_VOID(context);
254     auto titleBarNode = AceType::DynamicCast<TitleBarNode>(node->GetTitleBarNode());
255     auto toolBarNode = AceType::DynamicCast<NavToolbarNode>(node->GetToolBarNode());
256     auto toolBarDividerNode = AceType::DynamicCast<FrameNode>(node->GetToolBarDividerNode());
257     if (needRunTitleBarAnimation && titleBarNode) {
258         property->UpdateTitleBarTranslateState(hideTitle ?
259             BarTranslateState::TRANSLATE_HEIGHT : BarTranslateState::TRANSLATE_ZERO);
260     }
261     if (needRunToolBarAnimation && toolBarNode) {
262         property->UpdateToolBarTranslateState(hideTool ?
263             BarTranslateState::TRANSLATE_HEIGHT : BarTranslateState::TRANSLATE_ZERO);
264     }
265     node->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
266     context->FlushUITasks();
267     if (needRunTitleBarAnimation && titleBarNode) {
268         UpdateTitleBarTranslateAndOpacity(hideTitle, titleBarNode, GetTitleBarHeight());
269     }
270     if (needRunToolBarAnimation && toolBarNode) {
271         UpdateToolBarAndDividerTranslateAndOpacity(hideTool, toolBarNode, GetToolBarHeight(),
272             toolBarDividerNode, GetToolBarDividerHeight());
273     }
274 }
275 
BarAnimationFinishCallback(bool needRunTitleBarAnimation,bool needRunToolBarAnimation,int32_t animationId)276 void NavDestinationPatternBase::BarAnimationFinishCallback(
277     bool needRunTitleBarAnimation, bool needRunToolBarAnimation, int32_t animationId)
278 {
279     if (needRunTitleBarAnimation) {
280         OnTitleBarAnimationFinish();
281     }
282     if (needRunToolBarAnimation) {
283         OnToolBarAnimationFinish();
284     }
285     RemoveAnimation(animationId);
286 }
287 
StartAnimation(bool needRunTitleBarAnimation,bool hideTitle,bool needRunToolBarAnimation,bool hideTool)288 void NavDestinationPatternBase::StartAnimation(
289     bool needRunTitleBarAnimation, bool hideTitle, bool needRunToolBarAnimation, bool hideTool)
290 {
291     auto propertyCallback = [needRunTitleBarAnimation, hideTitle, needRunToolBarAnimation, hideTool,
292                                 weakPattern = AceType::WeakClaim(this)]() {
293         auto pattern = weakPattern.Upgrade();
294         CHECK_NULL_VOID(pattern);
295         pattern->BarAnimationPropertyCallback(needRunTitleBarAnimation, hideTitle, needRunToolBarAnimation, hideTool);
296     };
297     auto finishCallback = [needRunTitleBarAnimation, needRunToolBarAnimation,
298                               weakPattern = AceType::WeakClaim(this), animationId = nextBarAnimationId_]() {
299         auto pattern = weakPattern.Upgrade();
300         CHECK_NULL_VOID(pattern);
301         pattern->BarAnimationFinishCallback(needRunTitleBarAnimation, needRunToolBarAnimation, animationId);
302     };
303 
304     AnimationOption option;
305     option.SetCurve(Curves::FAST_OUT_SLOW_IN);
306     option.SetDuration(DEFAULT_ANIMATION_DURATION);
307     if (needRunTitleBarAnimation) {
308         OnTitleBarAnimationStart();
309     }
310     if (needRunToolBarAnimation) {
311         OnToolBarAnimationStart();
312     }
313     auto animation = AnimationUtils::StartAnimation(option, propertyCallback, finishCallback);
314     barAnimations_.emplace(nextBarAnimationId_, animation);
315     nextBarAnimationId_++;
316 }
317 
OnTitleBarAnimationFinish()318 void NavDestinationPatternBase::OnTitleBarAnimationFinish()
319 {
320     titleBarAnimationCount_--;
321     if (titleBarAnimationCount_ > 0) {
322         return;
323     }
324 
325     auto node = AceType::DynamicCast<NavDestinationNodeBase>(GetHost());
326     CHECK_NULL_VOID(node);
327     auto property = node->GetLayoutProperty<NavDestinationLayoutPropertyBase>();
328     CHECK_NULL_VOID(property);
329     property->ResetTitleBarTranslateState();
330     HideOrShowTitleBarImmediately(node, property->GetHideTitleBarValue(false));
331 }
332 
OnToolBarAnimationFinish()333 void NavDestinationPatternBase::OnToolBarAnimationFinish()
334 {
335     toolBarAnimationCount_--;
336     if (toolBarAnimationCount_ > 0) {
337         return;
338     }
339 
340     auto node = AceType::DynamicCast<NavDestinationNodeBase>(GetHost());
341     CHECK_NULL_VOID(node);
342     auto property = node->GetLayoutProperty<NavDestinationLayoutPropertyBase>();
343     CHECK_NULL_VOID(property);
344     property->ResetToolBarTranslateState();
345     HideOrShowToolBarImmediately(node, property->GetHideToolBarValue(false));
346 }
347 
AbortBarAnimation()348 void NavDestinationPatternBase::AbortBarAnimation()
349 {
350     for (const auto& pair : barAnimations_) {
351         if (pair.second) {
352             AnimationUtils::StopAnimation(pair.second);
353         }
354     }
355     barAnimations_.clear();
356 }
357 
RemoveAnimation(int32_t id)358 void NavDestinationPatternBase::RemoveAnimation(int32_t id)
359 {
360     auto it = barAnimations_.find(id);
361     if (it != barAnimations_.end()) {
362         barAnimations_.erase(it);
363     }
364 }
365 
UpdateHideBarProperty()366 void NavDestinationPatternBase::UpdateHideBarProperty()
367 {
368     auto hostNode = GetHost();
369     CHECK_NULL_VOID(hostNode);
370     auto layoutProperty = hostNode->GetLayoutProperty<NavDestinationLayoutPropertyBase>();
371     CHECK_NULL_VOID(layoutProperty);
372     /**
373      *  Mark need update safeAreaPadding when usr-set visibility of safe-area-padding-mode titleBar changed.
374      *  The same goes for toolBar.
375      */
376     if ((titleBarStyle_.value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING &&
377         isHideTitlebar_ != layoutProperty->GetHideTitleBarValue(false)) ||
378         (toolBarStyle_.value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING &&
379         isHideToolbar_ != layoutProperty->GetHideToolBarValue(false))) {
380         safeAreaPaddingChanged_ = true;
381     }
382     isHideToolbar_ = layoutProperty->GetHideToolBarValue(false);
383     isHideTitlebar_ = layoutProperty->GetHideTitleBarValue(false);
384 }
385 
ExpandContentSafeAreaIfNeeded()386 void NavDestinationPatternBase::ExpandContentSafeAreaIfNeeded()
387 {
388     auto hostNode = DynamicCast<NavDestinationNodeBase>(GetHost());
389     CHECK_NULL_VOID(hostNode);
390     auto layoutProperty = hostNode->GetLayoutProperty<NavDestinationLayoutPropertyBase>();
391     CHECK_NULL_VOID(layoutProperty);
392     auto&& opts = layoutProperty->GetSafeAreaExpandOpts();
393     auto contentNode = AceType::DynamicCast<FrameNode>(hostNode->GetContentNode());
394     if (opts && contentNode) {
395         TAG_LOGI(AceLogTag::ACE_NAVIGATION, "%{public}s SafeArea expand as %{public}s",
396             hostNode->GetTag().c_str(), opts->ToString().c_str());
397         contentNode->GetLayoutProperty()->UpdateSafeAreaExpandOpts(*opts);
398         contentNode->MarkModifyDone();
399     }
400 }
401 
MarkSafeAreaPaddingChangedWithCheckTitleBar(float titleBarHeight)402 void NavDestinationPatternBase::MarkSafeAreaPaddingChangedWithCheckTitleBar(float titleBarHeight)
403 {
404     auto hostNode = DynamicCast<NavDestinationNodeBase>(GetHost());
405     CHECK_NULL_VOID(hostNode);
406     if (titleBarStyle_.value_or(BarStyle::STANDARD) != BarStyle::SAFE_AREA_PADDING) {
407         return;
408     }
409     /**
410      *  Mark need update safeAreaPadding when the height of safe-area-padding-title changed.
411      *  For example, when titleMode of navigation changed or when free-mode-title is dragged.
412      */
413     if (!NearEqual(titleBarHeight, titleBarHeight_)) {
414         safeAreaPaddingChanged_ = true;
415         return;
416     }
417     /**
418      *  Mark need update safeAreaPadding when titleBar onHover mode updated.
419      */
420     auto titleBarNode = AceType::DynamicCast<TitleBarNode>(hostNode->GetTitleBarNode());
421     if (titleBarNode && NavigationTitleUtil::CalculateTitlebarOffset(titleBarNode) != titleBarOffsetY_) {
422         safeAreaPaddingChanged_ = true;
423     }
424 }
425 } // namespace OHOS::Ace::NG