/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* 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 { common } from '@kit.AbilityKit';
import { router } from '@kit.ArkUI';
import {
BreakpointTypeEnum,
CommonConstants,
ContinueModel,
EventTypeEnum,
Logger,
UserAccount,
WindowUtil
} from '@ohos/utils';
import Constants from '../constants/Constants';
import { TabInfo, UserModel } from '../model/UserModel';
import { AchievesView } from '../components/AchievesView';
import { CollectedResourceView } from '../components/CollectedResourcesView';
import { ViewedResourceView } from '../components/ViewedResourceView';
import { SettingView } from './SettingView';
const TAG = '[MineView]';
let continueModel = ContinueModel.getInstance();
/**
* Pull to reload offset const value
*/
const RELOAD_ANIMATION_DELAY = 1500;
@Component
export struct MineView {
private eventHub: common.EventHub = (getContext(this) as common.UIAbilityContext).eventHub;
@State userModel: UserModel = UserModel.getInstance();
@State currentIndex: TabInfo = TabInfo.COLLECTED;
/**
* Whether to trigger a refresh, the value is controlled by the scroll example;
*/
@State isRefresh: boolean = false;
/**
* Y-axis drag distance;
*/
@State offsetY: number = 0;
/**
* Control the disappear and appear of user image;
*/
private outScroller: Scroller = new Scroller();
private innerScroller: Scroller = new Scroller();
/**
* User timer to limit the request count of reload;
*/
private timer: number = 0;
@State isShowSmallImg: boolean = false;
@StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
@Builder
MineTabs(index: number, barName: Resource) {
Column() {
Text(barName)
.fontColor(index === this.currentIndex ? $r('sys.color.ohos_id_color_subtab_text_on') :
$r('sys.color.ohos_id_color_text_secondary'))
.fontWeight(index === this.currentIndex ? FontWeight.Medium : FontWeight.Normal)
.fontSize($r('app.float.tabbar_font_size'))
.maxLines(1)
.margin({ top: $r('app.float.sm_padding_margin'), bottom: $r('app.float.mine_tab_margin') })
Divider()
.strokeWidth(2)
.lineCap(LineCapStyle.Round)
.width($r('app.float.divider_width_32'))
.color(index === this.currentIndex ? $r('sys.color.ohos_id_color_subtab_text_on') :
$r('app.color.hmos_color_white'))
}
.width(CommonConstants.FULL_PERCENT)
.height(CommonConstants.FULL_PERCENT)
.justifyContent(FlexAlign.Center)
}
@Builder
MineBackGroundImage() {
// Background Image
Column() {
Image($r('app.media.user_background_image'))
.width(CommonConstants.FULL_PERCENT)
.height(this.calImageHeight())
.objectFit(ImageFit.Cover)
}
.width(CommonConstants.FULL_PERCENT)
.aspectRatio(3 / 2)
}
@Builder
MineRefresh() {
LoadingProgress()
.size({
width: $r('app.float.login_size'),
height: $r('app.float.login_size')
})
.width(CommonConstants.FULL_PERCENT)
.color(Color.White)
}
dynamicLoading(): void {
try {
import('./SettingView');
import('./PhotoView');
} catch (err) {
Logger.error(TAG, 'dynamicLoading error:' + err);
}
}
aboutToAppear(): void {
this.dynamicLoading();
WindowUtil.getWindowUtil().updateStatusBarColor(getContext(this), true);
if (continueModel.isContinue) {
this.currentIndex = continueModel.data.mineTabIndex;
}
}
calImageHeight(): number {
return (this.offsetY + 150) * 2.5;
}
build() {
Navigation() {
Stack({ alignContent: Alignment.Top }) {
// BackgroundImage view;
if (this.currentBreakpoint !== BreakpointTypeEnum.LG) {
this.MineBackGroundImage()
}
Column() {
// Header include user small image and setting.
MineHeader({ isShowSmallImg: this.isShowSmallImg })
Refresh({ refreshing: $$this.isRefresh, builder: this.MineRefresh() }) {
List({ scroller: this.outScroller }) {
// User image and information;
ListItem() {
UserInfoView()
}
.onVisibleAreaChange([0, 1], (isVisible: boolean, currentRatio: number) => {
if (currentRatio == 0 && !isVisible && this.outScroller.currentOffset().yOffset !== 0) {
this.isShowSmallImg = true;
} else if (currentRatio == 1 && isVisible) {
this.isShowSmallImg = false;
}
})
// Tabs for user collected 、viewed resource and achievements.
ListItem() {
Tabs({ index: this.currentIndex }) {
TabContent() {
CollectedResourceView({ innerScroller: this.innerScroller, outerScroller: this.outScroller })
}
.padding({
top: $r('app.float.md_padding_margin'),
bottom: $r('app.float.md_padding_margin_bottom')
})
.tabBar(this.MineTabs(TabInfo.COLLECTED, $r('app.string.my_collected')))
.backgroundColor($r('app.color.hmos_background_color_white'))
TabContent() {
ViewedResourceView({ innerScroller: this.innerScroller, outerScroller: this.outScroller })
}
.tabBar(this.MineTabs(TabInfo.VIEWED, $r('app.string.my_viewed')))
.backgroundColor($r('app.color.hmos_background_color_white'))
.padding({
top: $r('app.float.md_padding_margin'),
bottom: $r('app.float.md_padding_margin_bottom')
})
TabContent() {
AchievesView()
}
.tabBar(this.MineTabs(TabInfo.ACHIEVEMENT, $r('app.string.my_achieve')))
.backgroundColor($r('app.color.hmos_background_color_white'))
.padding({ top: $r('app.float.md_padding_margin') })
}
.backgroundColor($r('app.color.hmos_color_white'))
.borderRadius($r('app.float.tabs_border_radius'))
.vertical(false)
.barMode(BarMode.Fixed)
.barWidth($r('app.float.tab_bar_width'))
.barHeight($r('app.float.tab_bar_height'))
.layoutWeight(1)
.onChange((index: number) => {
this.currentIndex = index;
continueModel.data.mineTabIndex = index;
})
}
}
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.None)
.width(CommonConstants.FULL_PERCENT)
.height(CommonConstants.FULL_PERCENT)
.layoutWeight(1)
}
.onRefreshing(() => {
this.timer = setTimeout(() => {
this.userModel.reload(this.currentIndex).then(() => {
if (this.currentBreakpoint === BreakpointTypeEnum.LG) {
this.eventHub.emit(EventTypeEnum.MINE_REFRESH);
}
});
this.isRefresh = false;
clearTimeout(this.timer);
}, RELOAD_ANIMATION_DELAY);
})
.onOffsetChange((value: number) => {
this.offsetY = value;
})
}
.height(CommonConstants.FULL_PERCENT)
}
}
.hideTitleBar(true)
.mode(NavigationMode.Stack)
}
}
@Component
struct MineHeader {
userModel: UserModel = UserModel.getInstance();
@Prop isShowSmallImg: boolean = false;
@State showSettingModal: boolean = false;
@StorageProp('isLogged') isLogged: boolean = false;
@StorageProp('user') account: UserAccount | null = null;
@StorageProp('profilePixelMap') profilePixelMap: PixelMap | null = null;
@StorageProp('foldExpanded') @Watch('foldExpandChange') foldExpanded: boolean = false;
@StorageProp('currentBreakpoint') @Watch('foldExpandChange') currentBreakpoint: string = BreakpointTypeEnum.MD;
foldExpandChange() {
let flag: boolean = false;
for (let i = 1; i <= parseInt(router.getLength()); i++) {
if (router.getState().name == 'SettingView') {
flag = true;
}
}
if (this.foldExpanded && this.currentBreakpoint === BreakpointTypeEnum.MD && flag) {
this.showSettingModal = true;
router.back();
} else if (!this.foldExpanded && this.currentBreakpoint === BreakpointTypeEnum.SM && this.showSettingModal) {
router.pushNamedRoute({ name: 'SettingView', params: undefined });
this.showSettingModal = false;
}
}
@Builder
settingModal() {
Column() {
SettingView()
}
.width($r('app.float.setting_modal_width'))
.height($r('app.float.setting_modal_height'))
}
build() {
Row() {
Blank()
Image($r('app.media.ic_topic'))
.width($r('app.float.topic_image_size'))
.aspectRatio(1)
.margin({ right: $r('app.float.topic_margin_right') })
.onClick(() => {
router.pushNamedRoute({ name: 'ThemeSettingPage' });
})
Image($r('app.media.ic_public_settings_white'))
.width($r('app.float.normal_icon_size'))
.height($r('app.float.normal_icon_size'))
.margin({ right: $r('app.float.topic_margin_right') })
.onClick(() => {
if (this.currentBreakpoint === BreakpointTypeEnum.SM) {
router.pushNamedRoute({ name: 'SettingView', params: undefined });
} else {
this.showSettingModal = true;
}
})
.bindSheet($$this.showSettingModal, this.settingModal(), {
preferType: SheetType.CENTER,
dragBar: false,
maskColor: $r('sys.color.ohos_id_color_fourth'),
backgroundColor: $r('sys.color.ohos_id_color_panel_bg'),
})
Image(this.profilePixelMap != null ? AppStorage.get<PixelMap>('profilePixelMap')
: (this.isLogged ? this.account?.portrait : $r('app.media.user_default')))
.size({
width: $r('app.float.account_image_size'),
height: $r('app.float.account_image_size')
})
.position({
x: Constants.ACCOUNT_POSITION_X,
y: this.isShowSmallImg ? $r('app.float.regular_margin') : $r('app.float.header_height')
})
.borderRadius($r('app.float.user_portrait_border_radius'))
.opacity(this.isShowSmallImg ? 1 : 0)
.animation({ duration: Constants.SHORT_ANIMATION_DURATION })
}
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Center)
.margin({ top: AppStorage.get<number>('statusBarHeight') })
.height($r('app.float.header_height'))
.width(CommonConstants.FULL_PERCENT)
}
}
@Component
struct UserInfoView {
@StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
@StorageProp('isLogged') isLogged: boolean = false;
@StorageProp('user') account: UserAccount | null = null;
@StorageProp('profilePixelMap') profilePixelMap: PixelMap | null = null;
build() {
Column() {
Row({ space: Constants.MIDDLE_SPACE }) {
Image(this.profilePixelMap != null ? AppStorage.get<PixelMap>('profilePixelMap')
: (this.isLogged ? this.account?.portrait : $r('app.media.user_default')))
.size({ width: $r('app.float.user_image_size'), height: $r('app.float.user_image_size') })
.borderRadius($r('app.float.xxl_border_radius'))
.margin({ bottom: $r('app.float.lg_padding_margin'), top: $r('app.float.lg_padding_margin') })
.onClick(() => {
router.pushNamedRoute({ name: 'PhotoView' });
})
Text(this.isLogged ? this.account?.nickname : $r('app.string.default_login'))
.fontColor($r('app.color.hmos_font_color_white'))
.fontSize($r('app.float.user_account_font_size'))
.padding({ bottom: $r('app.float.login_account_padding_bottom') })
.onClick(() => {
if (!this.isLogged) {
router.pushNamedRoute({ name: 'LoginPage' });
}
})
}
.width(CommonConstants.FULL_PERCENT)
.justifyContent(this.isLogged ? FlexAlign.Start : FlexAlign.Center)
Text(this.isLogged ? this.account?.description : $r('app.string.default_description'))
.width(CommonConstants.FULL_PERCENT)
.fontSize($r('app.float.title_font_size'))
.fontColor($r('app.color.hmos_font_color_white'))
}
.alignItems(HorizontalAlign.Start)
.padding({ left: $r('app.float.xxl_padding_margin') })
.width(CommonConstants.FULL_PERCENT)
.height(this.currentBreakpoint === BreakpointTypeEnum.LG ? $r('app.float.user_background_height_md') :
$r('app.float.user_background_height_lg'))
}
}