The following case demonstrates how to build a world clock application using HarmonyOS NEXT. This application supports dynamic time updates for multiple cities, search filtering, and highlighted text matching. Below is the code implementation with detailed explanations and optimized English comments.
Core Code Implementation
1. Data Model Class
import { i18n } from '@kit.LocalizationKit'; // Import localization module
import { inputMethod } from '@kit.IMEKit'; // Import input method module
@ObservedV2 // Observer decorator for state management
class CityTimeInfo {
@Trace cityName: string = ""; // City name
@Trace currentTime: string = ""; // Current formatted time string
timeZone: i18n.TimeZone; // Time zone object
constructor(cityName: string, timeZone: i18n.TimeZone) {
this.cityName = cityName;
this.timeZone = timeZone;
}
@Trace isVisible: boolean = true; // Visibility flag for filtering
}
2. Main Component Structure
@Entry // Entry component decorator
@Component // Component decorator
struct WorldClockApp {
@State private searchText: string = ''; // Search keyword
@State private cityTimeList: CityTimeInfo[] = []; // List of city time data
private lineColor: string = "#e6e6e6"; // Border color
private titleBackgroundColor: string = "#f8f8f8"; // Header background color
private textColor: string = "#333333"; // Text color
private basePadding: number = 4; // Base padding
private lineWidth: number = 2; // Border width
private rowHeight: number = 50; // Row height
private ratio: number[] = [1, 1]; // Column width ratio
private textSize: number = 14; // Base font size
private updateIntervalId = 0; // Timer ID for updates
// Update time for all cities
updateAllCityTimes() {
const locale = i18n.System.getSystemLocale(); // Get system locale
for (const cityTime of this.cityTimeList) {
const timeZoneId: string = cityTime.timeZone.getID();
const calendar = i18n.getCalendar(locale);
calendar.setTimeZone(timeZoneId);
// Format time components
const year = calendar.get("year").toString().padStart(4, '0');
const month = (calendar.get("month") + 1).toString().padStart(2, '0');
const day = calendar.get("date").toString().padStart(2, '0');
const hour = calendar.get("hour_of_day").toString().padStart(2, '0');
const minute = calendar.get("minute").toString().padStart(2, '0');
const second = calendar.get("second").toString().padStart(2, '0');
cityTime.currentTime = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
}
// Timer control for updates
onPageShow(): void {
clearInterval(this.updateIntervalId);
this.updateIntervalId = setInterval(() => {
this.updateAllCityTimes();
}, 1000);
}
onPageHide(): void {
clearInterval(this.updateIntervalId);
}
// Highlight search text and filter cities
private highlightSearchText(cityTime: CityTimeInfo, keyword: string) {
let text = cityTime.cityName;
if (!keyword) {
cityTime.isVisible = true;
return [text];
}
let segments: string[] = [];
let lastMatchEnd: number = 0;
while (true) {
const matchIndex = text.indexOf(keyword, lastMatchEnd);
if (matchIndex === -1) {
segments.push(text.slice(lastMatchEnd));
break;
} else {
segments.push(text.slice(lastMatchEnd, matchIndex));
segments.push(text.slice(matchIndex, matchIndex + keyword.length));
lastMatchEnd = matchIndex + keyword.length;
}
}
cityTime.isVisible = segments.includes(keyword);
return segments;
}
// Initialize city data
aboutToAppear() {
const timeZoneIds: Array<string> = i18n.TimeZone.getAvailableIDs();
this.cityTimeList.push(new CityTimeInfo('Beijing (China)', i18n.getTimeZone()));
for (const id of timeZoneIds) {
const cityDisplayName = i18n.TimeZone.getCityDisplayName(id.split('/')[1], "zh-CN");
if (cityDisplayName) {
this.cityTimeList.push(new CityTimeInfo(cityDisplayName, i18n.getTimeZone(id)));
}
}
this.updateAllCityTimes();
}
// UI Layout
build() {
Column({ space: 0 }) {
Search({ value: $$this.searchText })
.margin(this.basePadding)
.fontFeature("\"ss01\" on");
Column() {
Row() {
Text('City')
.layoutWeight(this.ratio[0])
.textAlign(TextAlign.Center)
.fontSize(this.textSize)
.fontWeight(600);
Line().width(this.lineWidth).backgroundColor(this.lineColor);
Text('Time')
.layoutWeight(this.ratio[1])
.textAlign(TextAlign.Center)
.fontSize(this.textSize)
.fontWeight(600);
}
.height(this.rowHeight)
.borderWidth(this.lineWidth)
.backgroundColor(this.titleBackgroundColor);
}.width('100%').padding({ left: this.basePadding, right: this.basePadding });
Scroll() {
Column() {
ForEach(this.cityTimeList, (item: CityTimeInfo) => {
Row() {
Text() {
ForEach(this.highlightSearchText(item, this.searchText), (segment: string) => {
ContainerSpan() {
Span(segment)
.fontColor(segment === this.searchText ? Color.White : Color.Black)
.textBackgroundStyle({
color: segment === this.searchText ? Color.Red : Color.Transparent
});
};
});
}
.layoutWeight(this.ratio[0]);
Line().width(this.lineWidth).backgroundColor(this.lineColor);
Text(item.currentTime)
.layoutWeight(this.ratio[1]);
}
.height(this.rowHeight)
.visibility(item.isVisible ? Visibility.Visible : Visibility.None);
});
}.width('100%');
}
.onScrollStart(() => this.onPageHide())
.onScrollStop(() => this.onPageShow())
.onTouch((event) => {
if (event.type == TouchType.Down) {
inputMethod.getController().stopInputSession();
}
});
}
}
}
Key Features Explained
1. Dynamic Time Updates
- The
updateAllCityTimes
method fetches the current time for each city using thei18n
module. - A timer updates all cities every second, triggered by
onPageShow
and paused byonPageHide
.
2. Search & Highlight
- The
highlightSearchText
method splits city names into segments, highlighting matches with red backgrounds. - Visibility of cities is toggled based on search results.
3. Performance Optimization
- Timer updates are paused during scrolling (
onScrollStart/onScrollStop
). - Input sessions are closed on touch events to improve responsiveness.
4. UI Layout
- Uses
Row
andColumn
components for responsive layouts. - Styled borders and spacing to enhance readability.