易得天气
创建数据实体类
export interface SelectCityData {
hot_international?: Array<CityData>;
hot_national?: Array<CityData>;
}
export interface LocationData {
address?: string
address_component?: AddressComponentData
}
export interface AddressComponentData {
nation?: string
province?: string
city?: string
district?: string
street?: string
street_number?: string
}
创建Model
export default class SelectCityModel {
obtainHotCityData(): Promise<ResultData<SelectCityData>> {
return netUtils.netGet<SelectCityData>(Api.SELECT_CITY_API)
}
searchCity(searchKey: string): Promise<ResultData<Array<CityData>>> {
return netUtils.netGet<Array<CityData>>(Api.SEARCH_CITY_API, new Map([
['keyword', searchKey],
]))
}
obtainLocationDataByLocation(location: geoLocationManager.Location): Promise<ResultData<LocationData>> {
return netUtils.netGet<LocationData>(Api.LOCATION_API, new Map([
['location', location.latitude + ',' + location.longitude],
]))
}
obtainAddedCityData(): Promise<Array<CityData>> {
return appDatabase.cityDataDao.query()
}
}
创建ViewModel
@ObservedV2
export default class SelectCityViewModel extends BaseViewModel {
@Trace selectCityData?: SelectCityData
@Trace searchResult?: Array<CityData>
@Trace addedCityData?: Array<CityData>
@Trace searchResultContentOpacity: number = 0
@Trace locationData?: LocationData
// 0-定位中
// 1-定位结束
@Trace locationState: number = 0
private model = new SelectCityModel()
obtainHotCityData() {
this.setViewState(ViewState.VIEW_STATE_LOADING)
this.model.obtainHotCityData()
.then((response: ResultData<SelectCityData>) => {
this.selectCityData = response.data
this.setViewState(ViewState.VIEW_STATE_SUCCESS)
this.obtainAddedCityData()
this.obtainLocationPermission()
})
.catch((e: Error) => {
this.setErrorMessage(e.message)
this.setViewState(ViewState.VIEW_STATE_ERROR)
})
}
searchCity(searchKey: string) {
this.model.searchCity(searchKey)
.then((response: ResultData<Array<CityData>>) => {
let searchResult = response.data
if (ArrayUtil.isEmpty(searchResult)) {
ToastUtil.showToast('无匹配城市')
}
this.searchResult = searchResult
})
.catch(() => {
this.searchResult = undefined
})
}
obtainAddedCityData() {
this.model.obtainAddedCityData()
.then(addedCityData => {
Logger.e('addedCityData = ' + JSON.stringify(addedCityData))
this.addedCityData = addedCityData
})
}
obtainLocationPermission() {
this.locationData = undefined
this.locationState = 0
const ps: Permissions[] = ['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION']
TaoYao.with(AppUtil.getContext())
.runtime()
.permission(ps)
.onGranted(() => {
this.onPermissionGranted()
})
.onDenied(() => {
Logger.e('权限申请失败')
this.locationData = undefined
this.locationState = 1
DialogHelper.showAlertDialog({
title: '权限设置',
content: $r('app.string.reason_location_permission'),
primaryButton: '取消',
secondaryButton: '去设置',
onAction: (action) => {
if (action == DialogAction.SURE) {
PermissionUtil.requestPermissionsEasy(ps)
.then(success => {
if (success) {
this.onPermissionGranted()
} else {
TaoYao.goToSettingPage(AppUtil.getContext())
}
})
}
}
})
})
.request()
}
private onPermissionGranted() {
Logger.e('权限申请成功')
this.startLocation()
.then(location => {
if (location) {
Logger.e('longitude = ' + location.longitude + ' latitude = ' + location.latitude)
this.model.obtainLocationDataByLocation(location)
.then(response => {
let locationData = response.result
this.locationData = locationData
this.locationState = 1
})
.catch(() => {
this.locationData = undefined
this.locationState = 1
})
} else {
this.locationData = undefined
this.locationState = 1
}
})
}
async startLocation(): Promise<geoLocationManager.Location | undefined> {
const isLocationEnabled = LocationUtil.isLocationEnabled()
Logger.e('isLocationEnabled = ' + isLocationEnabled)
if (!isLocationEnabled) {
return undefined
}
return LocationUtil.getCurrentLocationEasy()
}
@Monitor("searchResult")
onSearchResultChange(monitor: IMonitor) {
Logger.e('onSearchResultChange')
let now = monitor.value<Array<CityData>>()?.now
setTimeout(() => {
this.searchResultContentOpacity = ArrayUtil.isEmpty(now) ? 0 : 1
}, 20)
}
clearSearchResult() {
this.searchResult = undefined
}
}
封装多状态组件
@ComponentV2
export struct MultipleStatusLayout {
@Param message: string = '暂无数据'
@Param viewState: string = ViewState.VIEW_STATE_LOADING
@BuilderParam contentView: () => void
build() {
Stack() {
if (this.viewState == ViewState.VIEW_STATE_LOADING) {
LoadingProgress()
.color($r('app.color.color_999999'))
.width(48)
.height(48)
} else if (this.viewState == ViewState.VIEW_STATE_ERROR) {
Text(this.message)
.fontColor($r('app.color.color_999999'))
.fontSize(14)
} else {
this.contentView()
}
}
.width('100%')
.height('100%')
}
}
MultipleStatusLayout使用示例
MultipleStatusLayout({
message: this.selectCityVm.errorMessage,
viewState: this.selectCityVm.viewState,
contentView: () => {
this.contentView()
}
})
选择城市用到的组件主要是List、Grid
@Builder
contentView() {
List() {
ListItem() {
this.locationButton()
}
ListItem() {
this.header('国内热门城市')
}
ListItem() {
this.content(this.selectCityVm.selectCityData?.hot_national)
}
ListItem() {
this.header('国际热门城市')
}
ListItem() {
this.content(this.selectCityVm.selectCityData?.hot_international)
}
}
.width('100%')
.height('100%')
.edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
}
@Builder
locationButton() {
Row({ space: 4 }) {
Image($r('app.media.writing_icon_location1'))
.width(18)
.height(18)
Text(this.selectCityVm.locationState == 0 ? '定位中...' :
(this.selectCityVm.locationData?.address_component?.district ?? '定位失败'))
.fontSize(13)
.fontColor($r('app.color.text_color_01'))
}
.clickEffect({ level: ClickEffectLevel.HEAVY, scale: 0.8 })
.padding({
left: 12,
top: 12,
right: 12,
bottom: 12
})
.backgroundColor($r('app.color.card_color_06'))
.borderRadius(100)
.margin({ left: 16, top: 12, bottom: 12 })
.onClick(() => {
if (!this.selectCityVm.locationData && this.selectCityVm.locationState == 1) {
this.selectCityVm.obtainLocationPermission()
}
})
}
@Builder
header(header: string) {
Text(header)
.fontColor($r('app.color.text_color_01'))
.fontSize(20)
.fontWeight(FontWeight.Bold)
.padding({ left: 16 })
.margin({ top: 8 })
}
@Builder
content(cityList?: Array<CityData>) {
Grid() {
ForEach(cityList, (cityData: CityData) => {
GridItem() {
Text(cityData.name)
.textAlign(TextAlign.Center)
.clickEffect({ level: ClickEffectLevel.HEAVY, scale: 0.8 })
.fontColor(this.hasAdded(cityData) ? $r('app.color.app_main') : $r('app.color.text_color_01'))
.fontSize(13)
.padding({ top: 12, bottom: 12 })
.backgroundColor($r('app.color.card_color_06'))
.borderRadius(100)
.width('100%')
.onClick(() => {
this.gotoWeatherPreviewPage(cityData)
})
}
}, (cityData: CityData): string => {
return cityData.cityid ?? ''
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(16)
.rowsGap(16)
.maxCount(4)
.layoutDirection(GridDirection.Row)
.padding({
left: 16,
top: 12,
right: 16,
bottom: 16
})
}
@Builder
searchResultContent() {
if (ArrayUtil.isNotEmpty(this.selectCityVm.searchResult)) {
List() {
ForEach(this.selectCityVm.searchResult, (cityData: CityData) => {
ListItem() {
Text(StrUtil.isEmpty(cityData.prov) ? cityData.name + ' - ' + cityData.country :
cityData.name + ' - ' + cityData.prov + ' - ' + cityData.country)
.clickEffect({ level: ClickEffectLevel.HEAVY, scale: 0.8 })
.fontColor(this.hasAdded(cityData) ? $r('app.color.app_main') : $r('app.color.text_color_01'))
.fontSize(15)
.padding({
left: 16,
top: 12,
right: 16,
bottom: 12
})
.fontWeight(FontWeight.Bold)
.width('100%')
.onClick(() => {
this.gotoWeatherPreviewPage(cityData)
})
}
}, (cityData: CityData): string => {
return cityData.cityid ?? ''
})
ListItem() {
Divider()
.strokeWidth(px2vp(AppUtil.getNavigationIndicatorHeight()))
.color($r('app.color.transparent'))
}
}
.width('100%')
.height('100%')
.edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
.animatableOpacity(this.selectCityVm.searchResultContentOpacity)
.animation({ duration: 222, curve: Curve.Linear })
.backgroundColor($r('app.color.bg_color'))
}
}
hasAdded(cityData: CityData) {
const find = this.selectCityVm.addedCityData?.find(it => it.cityid == cityData.cityid)
return find != undefined
}
gotoWeatherPreviewPage(cityData: CityData) {
ZRouter.getInstance()
.withParam(Constants.CITY_DATA, cityData)
.setPopListener(() => {
setTimeout(() => {
AppRuntimeData.getInstance().addCity(cityData)
}, 500)
})
.push(RouterConstants.WEATHER_PREVIEW_PAGE)
}
@AnimatableExtend(List)
function animatableOpacity(opacity: number) {
.opacity(opacity)
}
添加城市天气预览页面(未完成)
@Route({ name: RouterConstants.WEATHER_PREVIEW_PAGE })
@ComponentV2
export struct WeatherPreviewPage {
@Computed
get previewCityData() {
return ZRouter.getInstance().getParamByKey<CityData>(Constants.CITY_DATA)
}
@Builder
functionButton(action: string, block: () => void) {
Text(action)
.textAlign(TextAlign.Center)
.clickEffect({ level: ClickEffectLevel.HEAVY, scale: 0.8 })
.fontColor($r('app.color.special_white'))
.fontSize(14)
.padding({ left: 16, right: 16 })
.backgroundColor(ColorUtils.alpha($r('app.color.special_black'), 0.5))
.borderRadius(100)
.height(32)
.onClick(block)
}
build() {
NavDestination() {
Stack() {
Text(this.previewCityData.name)
.fontSize(22)
.height('100%')
Row() {
this.functionButton('取消', () => {
ZRouter.getInstance().pop()
})
this.functionButton('添加', () => {
ZRouter.getInstance().popWithResult(this.previewCityData)
})
}
.width('100%')
.padding({ left: 12, top: px2vp(AppUtil.getStatusBarHeight()) + 12, right: 12 })
.justifyContent(FlexAlign.SpaceBetween)
}
.alignContent(Alignment.Top)
}
.hideTitleBar(true)
.height('100%')
.width('100%')
.onReady((context) => {
ZRouter.animateMgr()
.registerAnimParam(this, context)
.setEnterAnimate({ duration: 500, curve: Curve.LinearOutSlowIn })
.setExitAnimate({ duration: 500, curve: Curve.LinearOutSlowIn })
.addAnimateOptions(new TranslateAnimationOptions({ y: '100%' }))
})
.onDisAppear(() => {
ZRouter.animateMgr().unregisterAnim(this)
})
.attributeModifier(ZRouter.animateMgr().modifier(this))
.backgroundColor(Color.Black)
.backgroundColor($r('app.color.bg_color'))
}
}
效果图
申请位置权限效果图:
添加城市效果图(添加城市的预览由于天气主页面还未完成,只展示城市名称):