HarmonyOS meta-service development practice: desktop card dictionary

1. Project Description

1.DEMO creativity is a card dictionary.

2. Different cards display different contents: micro card, small card, medium card, large card. Different contents of the same word are displayed according to different card characteristics. Users can choose their favorite card based on their habits.

3. Universal card refresh: Users click the card refresh button to view new content. At the same time, the card is set to refresh regularly, so that the cards users see every day are new text, making it easier for users to learn and review.

4. There is a search function in the meta-service. Users can search for corresponding words and explanations, and use the up and down sliding method similar to the current user habit to elaborate word by word.

5. Based on API9 and ArkTS language development, registration, login and other functions are realized through serverless cloud services.

2. Yuan service effect

  1. Universal card effect

Card plus table gif-1.gif

Card plus table gif-2.gif

2. Meta service inner page

Metaservice internal page demonstration operation gif.gif

3. Project Development

  1. Environment setup

Software requirements:

DevEco Studio version: DevEco Studio 3.1 Release and above.

HarmonyOS SDK version: API version 9 and above.

Hardware requirements:

Device type: Huawei mobile phone or Huawei mobile phone device simulator running on DevEco Studio.

HarmonyOS system: 3.1.0 Developer Release and above.

2. Interpretation of main code structure

Picture 14.png

entry/src/main/ets: file entry

common: public resource files

images: public image resources

Constants.ts: public constants

CountryViewModel.ts: Country number class

LazyFE_Class.ets: Lazy data loading class

Log.ts: log class

components: package component file

database: database encapsulation class

data_cyhz.ets: data file

entryability: application/service entry

entryformability: card service

pages: application/service pages

Auth.ets: Authentication and authorization

CloudFunction.ets: Cloud functions

CloudProject.ets: Cloud Projects

CloudStorage.ets: cloud storage

Index.ets: Home Page

User_Login.ets: Login page

User_SignUp.ets: Registration page

User_VerifyCodeLogin.ets: Verification code login page

services: background operation class

widget: service card

resources: resource file directory

3. Enter the application instructions

Since we are creating a cloud template project, there is no need to configure additional SDK dependencies. Just note that the initial integrated SDK location of the cloud template is different, so we still use context to initialize the SDK during the application initialization phase. It is recommended to do it in onCreate of entryability. .

Picture 15.png

4.Home page

We need to add a bottom menu bar to the application to switch between different application modules. Since each module is completely independent and does not need to refresh the interface every time it is switched, we use Tabs and TabContent components.

Picture 16.png

This application has a total of two modules: Home Page and Me, which correspond to the two sub-components of the Tabs component, TabContent.

The homepage contains two modules: search text and sliding browsing information. We will explain the specific code implementation in modules below.

Search text: The Search component is mainly used to jump to the corresponding text display information by searching for text. The main code is as follows:

Picture 17.png

...
//Commonly used Chinese character search bar
Column() {
  Search({ value: this.submitValue, placeholder: 'Chinese character search', controller: this.search })
    .searchButton('Search')
    .placeholderColor(Color.Grey)
    .textFont({ size: 14, weight: 400 })
    .margin({ left: 20, right: 20 })
    .onSubmit((value: string) => {
      this.submitValue = value
      for (let i = 0; i < wz.length; i + + ) {
        const element: any = wz[i];
        if (this.submitValue == element.zi) {
          this.swiperIndex = i
          this.submitValue = ''
        }
      }
    })
    .onChange((value: string) => {
      this.changeValue = value
    })
}.width("100%").margin({ top: 20, bottom: 20 })</code>Copy

Browsing information module: It mainly uses the swiper component to pre-cache data through lazy loading of data and display text information by sliding the page. The main code is as follows:

Picture 18.png

...

//Commonly used Chinese character carousel part
Column() {
  Swiper(this.swiperController) {
    LazyForEach(this.data_wz, (item: any) => {
      Column() {
        ...
      }.width("100%")
      .height("100%")
      .justifyContent(FlexAlign.Start)
      .alignItems(HorizontalAlign.Start)
    }, item => item)
  }
  .vertical(true)
  .cachedCount(2)
  .autoPlay(false)
  .indicator(false)
  .loop(false)
  .duration(400)
  .itemSpace(0)
  .curve(Curve.Linear)
  .cachedCount(3)
  .index(this.swiperIndex)
  .disableSwipe(this.disableSwipe)
  .onChange((index: number) => {
    console.info("swiper:" + index.toString())
    this.swiperIndex = index
  })
}.width("100%")

...
</code>Copy

5.My

My page contains two modules: visitor login and user login.

Among them, the login information and some application functions are not displayed when the visitor logs in, and only some application capabilities can be used;

User login displays some user information and expands all functions of the application, which requires user registration and login;

We will explain the specific code implementation in modules below.

Guest login:

Picture 19.png

...

//Visitor login status
if (this.isVisitor) {
  //Avatar information
  Column() {
    Image($r('app.media.icon'))
      .width(90)
      .objectFit(ImageFit.Contain)
      .borderRadius(50)
    Text(this.isVisitor ? "Visitor_" : this.userName).fontSize(16).margin(20)
    Button(this.isLogin ? "Logout" : "Login", { type: ButtonType.Capsule })
      .fontSize(14)
      .width('120')
      .height('30')
      .backgroundColor(0xf48fb1)
      .onClick(() => {
        router.replaceUrl({
          url: "pages/User_Login"
        })
      })
  }
  .width('90%')
  .height('240')
  .borderRadius(12)
  .margin({ top: 20 })
  .backgroundColor(0xFFFFFF)
  .shadow({ radius: 12, color: 0xCECECE, offsetX: 4, offsetY: 6 })
  .justifyContent(FlexAlign.Center)
}

...</code>Copy

User login:

Picture 20.png

...

//Already logged in status
  if (!this.isVisitor) {
    //Avatar information
    Column() {
...
      }
    .width('90%')
    .height('240')
    .borderRadius(12)
    .margin({ top: 20 })
    .backgroundColor(0xFFFFFF)
    .shadow({ radius: 12, color: 0xCECECE, offsetX: 4, offsetY: 6 })
    .justifyContent(FlexAlign.Center)

    //options
    Column() {
      ...
}.width('100%')
.height("100%")
.backgroundColor(0xF5F5F5)
.justifyContent(FlexAlign.Start)</code>Copy

6.Registration login page

Allow users to register an account and fully use the application.

Picture 21.png

Picture 22.png

Core code:

...

.onClick(() => {
  if (this.phoneNumber !== '' & amp; & amp; this.password !== '') {
    let verifyCodeSettings = new VerifyCodeSettingBuilder()
      .setAction(VerifyCodeAction.REGISTER_LOGIN)
      .setLang('zh_CN')
      .setSendInterval(60)
      .build();
    agconnect.auth().requestPhoneVerifyCode(this.countryCode, this.phoneNumber, verifyCodeSettings)
      .then(verifyCodeResult => {
        this.startTimer()
        //Verification code application successful
      }).catch(error => {
      //Verification code application failed
      Prompt.showToast({ message: "Please enter the correct mobile phone number and password" + JSON.stringify(error) })
    });
  }else {
    Prompt.showToast({ message: "Mobile phone number and password cannot be empty" })
  }
})

...
...

.onClick(() => {
  if (this.phoneNumber !== '' & amp; & amp; this.password !== '') {
    let user = new PhoneUserBuilder()
      .setCountryCode(this.countryCode)
      .setPhoneNumber(this.phoneNumber)
      .setPassword(this.password) //You can set an initial password for the user. After filling in, you can use your password to log in later.
      .setVerifyCode(this.VerifyCode)
      .build();
    agconnect.auth().createPhoneUser(user)
      .then(result => {
        //Create user successfully
        AppStorage.Set('phoneNumber', user.phoneNumber)
        AppStorage.Set('password', user.password)
        AppStorage.Set('isVisitor', false)
        AppStorage.Set('isLogin', true)
        AppStorage.Set('userName', user.phoneNumber)
        router.pushUrl({
          url: "pages/Index"
        })
      })
      .catch(error => {
        //Failed to create user
        Prompt.showToast({ message: "Registration failed," + JSON.stringify(error),duration:4 })
      })
  } else {
    Prompt.showToast({ message: "Mobile phone number and password cannot be empty" })
  }
})</code>Copy

7. Other cloud services

Note: This is the initial business of the cloud template. If you have other business needs, you can add it yourself.

Picture 23.png

4. Card Development

Add cards as needed, or just create the card initially and modify the relevant card parameters.

  1. Create card

Picture 24.png

Picture 25.png

Picture 26.png

2. Initial card modification related parameters

Open the form_config.json file in the resources/base/profile directory and modify the parameters as needed

Picture 27.png

Picture 28.png

3. Card loading and data acquisition

The card loading and updating part is completed by the EntryFormAbility.ts file. You can refer to the official documentation here.

Since there is no connection to the background data part, we use custom simulation data here, and then randomly select the card content each time the card is mounted to the desktop. The code is as follows:

Picture 29.png

Picture 30.png

...
aboutToAppear() {
  let idx = Math.floor((Math.random() * wz_arr.length))
  this.zi = wz_arr[idx].zi
  this.pinYin = wz_arr[idx].pinYin
  this.buShou = wz_arr[idx].buShou
  this.biHua = wz_arr[idx].biHua
  this.fanTi = wz_arr[idx].fanTi
  this.near_words = wz_arr[idx].near_words
  this.reverse_words = wz_arr[idx].reverse_words
  this.explain = wz_arr[idx].explain.toString()
}

...</code>Copy

4. Card master code

Picture 31.png

...

Column() {
  //Micro card
  Stack() {
    Text(this.zi)
      .width("100%")
      .textAlign(TextAlign.Center)
      .fontSize(30)
      .fontColor('#1f1f1f')
      .fontWeight(600)
      .margin({right:20})
    Row(){
      Image("/common/images/R2.png")
        .width(18)
        .height(18)
        .margin({right:"15%"})
        .objectRepeat(ImageRepeat.NoRepeat)
        .onClick(()=>{
          this.rotateAngle = 180
          this.aboutToAppear()
        })
        .rotate({ angle: this.rotateAngle })
        .animation({
          duration:300,
          curve: Curve.Linear,
          playMode: PlayMode.Normal
        })
    }.width("100%").justifyContent(FlexAlign.End)

  }
  .width("100%")
  .height(72)
  //Small card, medium card
  Flex({direction:FlexDirection.Column,wrap:FlexWrap.Wrap,justifyContent:FlexAlign.Start}){
    Column(){
      Text("Pinyin:" + this.pinYin).fontSize(14).margin({left:15})
      Text("Radical:" + this.buShou).fontSize(14).margin({top:4,left:15})
      Text("Stroke:" + this.biHua).fontSize(14).margin({top:4,left:15})
      Text("Traditional Chinese:" + this.fanTi).fontSize(14).margin({top:4,left:15})
    }.width(208)
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Start)

    Column(){
      Text("Synonyms:" + this.near_words).fontSize(12).margin({right:15})
      Text("Antonyms:" + this.reverse_words).fontSize(12).margin({top:4,right:15})
    }
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Start)
  }
  .width("100%")
  .height(102)
  //Big card
  Column(){
    Text("Explanation:").width("100%").textAlign(TextAlign.Start).fontSize(12).margin({left:15,right:15})
    Text(this.explain).fontSize(14).margin({top:4,left:15,right:15})
  }.width("100%")
  .height("100%")
  .justifyContent(FlexAlign.Start)
  .alignItems(HorizontalAlign.Start)
}
.width("100%")
.alignItems(HorizontalAlign.Center)
.backgroundImage("/common/images/cywz.jpg")
.backgroundImageSize(ImageSize.Cover)
.onClick(() => {
  postCardAction(this, {
    "action": this.ACTION_TYPE,
    "abilityName": this.ABILITY_NAME,
    "params": {
      "message": this.MESSAGE,
    }
  });
})

...
</code>Copy

5. Project Operation

Picture 32.png

Picture 33.png

Picture 34.png

6. Conclusion

Interested developers can click to enter the official website of Yuan Service to learn more about Yuan Service and Universal Card related information. You can also experience the meta-service – Idiom Mood, which the author and team of this article have launched and operated on the negative screen of Huawei mobile phones and the meta-service area of Huawei App Market. Use universal cards to refresh idioms according to your mood.

Picture 35.png

Click to enter Huawei’s official website to unlock more exciting content