Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes: live search, parallel querying of weather data, handling of missing data, layout improvements. #6

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions entry/src/main/ets/model/WeatherModel.ets
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,9 @@ export interface DailyUnits {
wind_direction_10m_dominant?: string;
shortwave_radiation_sum?: string;
et0_fao_evapotranspiration?: string;
}

export interface WeatherData {
currentWeather: CurrentWeather | null;
forecast: Forecast | null;
}
5 changes: 4 additions & 1 deletion entry/src/main/ets/view/CityDetailsComponent.ets
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ export struct CityDetailsComponent {
Column() {
if (this.city) {
CurrentWeatherComponent({ city: this.city, currentWeather: this.currentWeather })
WeekForecastComponent({ forecast: this.forecast })

if (this.forecast)
WeekForecastComponent({ forecast: this.forecast })

Blank(100)
}
}
Expand Down
143 changes: 83 additions & 60 deletions entry/src/main/ets/view/CurrentWeatherComponent.ets
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TemperatureIcon } from './ui/TemperatureIcon';
import { WeatherIcon } from './ui/WeatherIcon';
import { CoordinateType, toDMS } from '../common/CoordinatesConverter';
import { City, CurrentWeather, Current, CurrentUnits, Forecast } from '../model/WeatherModel';
import { City, CurrentWeather, Current, CurrentUnits } from '../model/WeatherModel';

@Component
export struct CurrentWeatherComponent {
Expand Down Expand Up @@ -30,7 +30,7 @@ export struct CurrentWeatherComponent {
.fillColor(Color.White)
.width(16)
.height(16)
.margin({right: 4});
.margin({ right: 4 });

Text(toDMS(this.city?.lat, CoordinateType.LATITUDE) + ", "
+ toDMS(this.city?.lon, CoordinateType.LONGITUDE))
Expand All @@ -40,73 +40,96 @@ export struct CurrentWeatherComponent {
color: 0x88000000,
offsetX: 1,
offsetY: 1,
radius: 1})
}.padding({top: 6, bottom: 6})
radius: 1 })
}.padding({ top: 6, bottom: 6 })

Row() {
Column() {

Row() {
Image($r('app.media.ic_small_wind')).width(16).margin({right: 4});
Text($r('app.string.wind')).fontColor(Color.White).fontSize(14).fontWeight(FontWeight.Bold)
}.margin({bottom: 2})

Text($r('app.string.wind_speed_10m', `${this.current?.wind_speed_10m} ${this.units?.wind_speed_10m}`)).fontColor(Color.White).fontSize(12).margin({left: 21})
Text($r('app.string.wind_direction_10m', `${this.current?.wind_direction_10m}${this.units?.wind_direction_10m}`)).fontColor(Color.White).fontSize(12).margin({left: 21})
Text($r('app.string.wind_gusts_10m', `${this.current?.wind_gusts_10m} ${this.units?.wind_gusts_10m}`)).fontColor(Color.White).fontSize(12).margin({left: 21})

Row() {
Image($r('app.media.ic_small_pressure')).width(16).margin({right: 4})
Text($r('app.string.pressure')).fontColor(Color.White).fontSize(14).fontWeight(FontWeight.Bold)
}.margin({top: 8, bottom: 2})
if (this.currentWeather) {

Text(`${this.current?.surface_pressure} ${this.units?.surface_pressure}`).fontColor(Color.White).fontSize(12).margin({left: 21})
Row() {
Column() {

Row() {
Image($r('app.media.ic_small_humidity')).width(16).margin({right: 4})
Text($r('app.string.humidity')).fontColor(Color.White).fontSize(14).fontWeight(FontWeight.Bold)
}.margin({top: 8, bottom: 2})
Row() {
Image($r('app.media.ic_small_wind')).width(16).margin({ right: 4 });
Text($r('app.string.wind')).fontColor(Color.White).fontSize(14).fontWeight(FontWeight.Bold)
}.margin({ bottom: 2 })

Text($r('app.string.relative_humidity_2m', `${this.current?.relative_humidity_2m}${this.units?.relative_humidity_2m}`)).fontColor(Color.White).fontSize(12).margin({left: 21})

}.layoutWeight(0.5)
.alignItems(HorizontalAlign.Start)
.margin({top: 8})

Column() {
WeatherIcon({
weatherCode: this.current?.weather_code,
isDay: this.current?.is_day === 1 ? true : false
}).width(128).height(128)
Text($r('app.string.wind_speed_10m', `${this.current?.wind_speed_10m} ${this.units?.wind_speed_10m}`))
.fontColor(Color.White)
.fontSize(12)
.margin({ left: 21 })
Text($r('app.string.wind_direction_10m', `${this.current?.wind_direction_10m}${this.units?.wind_direction_10m}`))
.fontColor(Color.White)
.fontSize(12)
.margin({ left: 21 })
Text($r('app.string.wind_gusts_10m', `${this.current?.wind_gusts_10m} ${this.units?.wind_gusts_10m}`))
.fontColor(Color.White)
.fontSize(12)
.margin({ left: 21 })

Row() {
TemperatureIcon({
temp: this.current?.temperature_2m
}).width(32);
Row() {
Image($r('app.media.ic_small_pressure')).width(16).margin({ right: 4 })
Text($r('app.string.pressure')).fontColor(Color.White).fontSize(14).fontWeight(FontWeight.Bold)
}.margin({ top: 8, bottom: 2 })

Text(`${this.current?.temperature_2m}`)
.fontSize(36)
.fontColor(Color.White)
.textShadow({
color: '#000000',
offsetX: 2,
offsetY: 2,
radius: 2
})

Text(`${this.units?.temperature_2m}`)
.fontSize(16)
Text(`${this.current?.surface_pressure} ${this.units?.surface_pressure}`)
.fontColor(Color.White)
.textShadow({
color: '#000000',
offsetX: 2,
offsetY: 2,
radius: 2
}).margin({top: -13, left: 3})
}
.fontSize(12)
.margin({ left: 21 })

}.layoutWeight(0.5).alignItems(HorizontalAlign.Center)
Row() {
Image($r('app.media.ic_small_humidity')).width(16).margin({ right: 4 })
Text($r('app.string.humidity')).fontColor(Color.White).fontSize(14).fontWeight(FontWeight.Bold)
}.margin({ top: 8, bottom: 2 })

Text($r('app.string.relative_humidity_2m', `${this.current?.relative_humidity_2m}${this.units?.relative_humidity_2m}`))
.fontColor(Color.White)
.fontSize(12)
.margin({ left: 21 })

}.width("50%")
.alignItems(HorizontalAlign.Start)
.margin({ top: 8 })

Column() {
WeatherIcon({
weatherCode: this.current?.weather_code,
isDay: this.current?.is_day === 1 ? true : false
}).width(128).height(128)

Row() {
TemperatureIcon({
temp: this.current?.temperature_2m
}).width(32);

Text(`${this.current?.temperature_2m}`)
.fontSize(36)
.fontColor(Color.White)
.textShadow({
color: '#000000',
offsetX: 2,
offsetY: 2,
radius: 2
})

Text(`${this.units?.temperature_2m}`)
.fontSize(16)
.fontColor(Color.White)
.textShadow({
color: '#000000',
offsetX: 2,
offsetY: 2,
radius: 2
}).margin({ top: -13, left: 3 })
}

}.width("50%").alignItems(HorizontalAlign.Center)
}.width("100%")

} else {
Text($r('app.string.no_weather_data'))
.fontColor(Color.White)
.fontSize(12)
.margin({top: 16, left: 4})
}

//Text(JSON.stringify(this.currentWeather)).fontSize(8).fontColor(Color.Gray);
Expand Down
28 changes: 13 additions & 15 deletions entry/src/main/ets/view/ResultsComponent.ets
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,22 @@ export struct ResultsComponent {

CityInfoItem({ city: item })

}.onClick(event => {
}.onClick(event => this.handleOnClick(item))

const cw = CategoryViewModel.getCurrentWeather(item)
const f = CategoryViewModel.getForecast(item)

this.selectedCity = item;

cw.then(currentWeather => {
this.currentWeather = currentWeather;
});

f.then(forecast => {
this.forecast = forecast;
this.viewState = ViewState.SHOW_CITY
});
})
})
}.height("90%").scrollBar(BarState.Off)
}
}

handleOnClick(city: City) {

this.selectedCity = city;
const weatherData = CategoryViewModel.getWeatherData(city);

weatherData.then(data => {
this.currentWeather = data.currentWeather;
this.forecast = data.forecast;
this.viewState = ViewState.SHOW_CITY
});
}
}
31 changes: 25 additions & 6 deletions entry/src/main/ets/view/SearchBarComponent.ets
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import { AppError } from '../common/AppError';
@Component
export struct SearchBarComponent {

private static readonly SEARCH_DELAY_MS: number = 500;

@Link cities: City[]
@Link viewState: ViewState
@Link error: AppError
@State submitValue: string = ''
@State changeValue: string = ''
controller: SearchController = new SearchController()
lastSearchTime: number = 0
searchTimeout: number = 0

build() {

Column() {

Search({ value: this.changeValue, placeholder: CommonConstants.SEARCH_PLACEHOLDER, controller: this.controller })
.enableKeyboardOnFocus(true)
.searchButton('SEARCH')
.height($r('app.float.search_height'))
.border({ radius: $r('app.float.search_radius') })
.shadow(ShadowStyle.OUTER_DEFAULT_SM)
Expand All @@ -37,15 +40,31 @@ export struct SearchBarComponent {
top: $r('app.float.search_margin_top'),
bottom: $r('app.float.search_margin_bottom')
})
.onChange((value: string) => {
console.debug('onChange: ', value);
this.changeValue = value;
})
.onChange((value: string) => this.handleOnChange(value))
.onSubmit((value: string) => this.handleOnSubmit(value))
}
}

handleOnSubmit(value: string) {
handleOnChange(value: string): void {
this.changeValue = value;

if (value.trim().length < 3) {
return;
}

const currentTime = Date.now();
if (currentTime - this.lastSearchTime < SearchBarComponent.SEARCH_DELAY_MS) {
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => this.handleOnSubmit(value), SearchBarComponent.SEARCH_DELAY_MS);

} else {
this.handleOnSubmit(value);
}

this.lastSearchTime = currentTime;
}

handleOnSubmit(value: string): void {

if (value.trim() === '') {
return
Expand Down
40 changes: 16 additions & 24 deletions entry/src/main/ets/viewmodel/CategoryViewModel.ets
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import hilog from '@ohos.hilog';
import { City, CurrentWeather, Forecast } from '../model/WeatherModel';
import { City, WeatherData } from '../model/WeatherModel';
import { WeatherAPIService } from '../net/WeatherAPIService';
import { MapBoxAPIService } from '../net/MapBoxAPIService';

export class CategoryViewModel {

Expand All @@ -13,36 +11,30 @@ export class CategoryViewModel {
return apiService.searchCity(query);
}

async getCurrentWeather(city: City): Promise<CurrentWeather|null> {
async getWeatherData(city: City): Promise<WeatherData> {
console.info('getWeatherData: ', city);

const apiService = new WeatherAPIService();

try {
const currentWeather = apiService.getCurrentWeather(city.lat, city.lon);
return currentWeather;

} catch (err) {

hilog.error(0x0000, 'CategoryViewModel', '%{public}s %{public}s ', 'Error: ', JSON.stringify(err));
}

return null;
}

async getForecast(city: City): Promise<Forecast|null> {

const apiService = new WeatherAPIService();

try {
const forecast = apiService.getForecast(city.lat, city.lon);
return forecast;
const data = await Promise.all([
apiService.getCurrentWeather(city.lat, city.lon),
apiService.getForecast(city.lat, city.lon)
]);

return {
currentWeather: data[0],
forecast: data[1]
};
} catch (err) {
console.info('Get weather data error: ', err);

hilog.error(0x0000, 'CategoryViewModel', '%{public}s %{public}s ', 'Error: ', JSON.stringify(err));
return {
currentWeather: null,
forecast: null
};
}

return null;
}
}

Expand Down
2 changes: 1 addition & 1 deletion entry/src/main/module.json5
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"srcEntry": "./ets/entryability/EntryAbility.ts",
"description": "$string:ability_desc",
"icon": "$media:icon",
"label": "$string:ability_label",
"label": "$string:app_name",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
Expand Down
10 changes: 8 additions & 2 deletions entry/src/main/resources/base/element/string.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"string": [

{
"name": "app_name",
"value": "MyMeteo"
},
{
"name": "time",
"value": "Time: %s"
Expand Down Expand Up @@ -81,7 +84,6 @@
"name": "wind_gusts_10m",
"value": "Gusts: %s"
},

{
"name": "module_desc",
"value": "This module template implements Category functions."
Expand Down Expand Up @@ -117,6 +119,10 @@
{
"name": "title",
"value": "Category"
},
{
"name": "no_weather_data",
"value": "No weather data..."
}
]
}
Loading