wangyujian_wd

feat:1)直播详情信息简介页和预约倒计时接口调试

@@ -466,6 +466,20 @@ export class DateTimeUtils { @@ -466,6 +466,20 @@ export class DateTimeUtils {
466 static getLunar(_date?: string) { 466 static getLunar(_date?: string) {
467 return getLunar(_date) 467 return getLunar(_date)
468 } 468 }
  469 +
  470 + /**
  471 + * 获取指定日期的时间戳
  472 + * @returns
  473 + */
  474 + static getDateTimestamp(date: string): number {
  475 + if (StringUtils.isEmpty(date))
  476 + return 0
  477 + try {
  478 + return new Date(date).getTime()
  479 + } catch (e) {
  480 + return 0
  481 + }
  482 + }
469 } 483 }
470 484
471 // const dateTimeUtils = new DateTimeUtils() 485 // const dateTimeUtils = new DateTimeUtils()
@@ -186,6 +186,11 @@ export class HttpUrlUtils { @@ -186,6 +186,11 @@ export class HttpUrlUtils {
186 */ 186 */
187 static readonly RELATED_SEARCH_CONTENT_DATA_PATH: string = "/api/rmrb-search-api/zh/c/suggestions/"; 187 static readonly RELATED_SEARCH_CONTENT_DATA_PATH: string = "/api/rmrb-search-api/zh/c/suggestions/";
188 /** 188 /**
  189 + * 直播详情
  190 + */
  191 + static readonly LIVE_DETAILS_PATH: string = "/api/rmrb-bff-display-zh/content/zh/c/content/detail";
  192 +
  193 + /**
189 * 早晚报列表 194 * 早晚报列表
190 * 根据页面id获取页面楼层列表 195 * 根据页面id获取页面楼层列表
191 * https://pdapis.pdnews.cn/api/rmrb-bff-display-zh/display/zh/c/pageInfo?pageId=28927 196 * https://pdapis.pdnews.cn/api/rmrb-bff-display-zh/display/zh/c/pageInfo?pageId=28927
@@ -555,6 +560,10 @@ export class HttpUrlUtils { @@ -555,6 +560,10 @@ export class HttpUrlUtils {
555 return url 560 return url
556 } 561 }
557 562
  563 + static getLiveDetailsUrl() {
  564 + let url = HttpUrlUtils._hostUrl + HttpUrlUtils.LIVE_DETAILS_PATH
  565 + return url
  566 + }
558 567
559 // static getYcgCommonHeaders(): HashMap<string, string> { 568 // static getYcgCommonHeaders(): HashMap<string, string> {
560 // let headers: HashMap<string, string> = new HashMap<string, string>() 569 // let headers: HashMap<string, string> = new HashMap<string, string>()
@@ -105,4 +105,6 @@ export { OperDataList } from './src/main/ets/bean/morningevening/OperDataList'; @@ -105,4 +105,6 @@ export { OperDataList } from './src/main/ets/bean/morningevening/OperDataList';
105 105
106 export { ShareInfo } from './src/main/ets/bean/morningevening/ShareInfo'; 106 export { ShareInfo } from './src/main/ets/bean/morningevening/ShareInfo';
107 107
108 -export { slideShows } from './src/main/ets/bean/morningevening/slideShows';  
  108 +export { slideShows } from './src/main/ets/bean/morningevening/slideShows';
  109 +
  110 +export { LiveDetailsBean } from './src/main/ets/bean/live/LiveDetailsBean';
  1 +export interface LiveDetailsBean {
  2 + /**
  3 + * {
  4 + "code": "0",
  5 + "data": [
  6 + {
  7 + "activityInfos": [],
  8 + "appstyle": 2,
  9 + "audioList": [],
  10 + "authorList": [
  11 + {
  12 + "authorName": "雷崔捷"
  13 + }
  14 + ],
  15 + "bestNoticer": null,
  16 + "commentDisplay": 0,
  17 + "editorName": "",
  18 + "firstFrameImageUri": "",
  19 + "fullColumnImgUrls": [
  20 + {
  21 + "format": null,
  22 + "height": null,
  23 + "landscape": null,
  24 + "size": null,
  25 + "url": "https://rmrbcmsonline.peopleapp.com/upload/image/202404/rmrb_71671711971849.png",
  26 + "weight": null
  27 + }
  28 + ],
  29 + "hasPopUp": null,
  30 + "isNewspaper": false,
  31 + "itemId": "",
  32 + "itemTypeCode": "",
  33 + "keyArticle": 0,
  34 + "likesStyle": null,
  35 + "liveInfo": {
  36 + "background": {
  37 + "imageUrl": "",
  38 + "name": ""
  39 + },
  40 + "backgroundStyle": null,
  41 + "cornerFlag": 0,
  42 + "cornerImgUrl": "",
  43 + "cornerLinkUrl": "",
  44 + "createUserId": "",
  45 + "createUserName": "",
  46 + "endTime": "2024-04-03 11:08:00",
  47 + "handAngleImageUri": "",
  48 + "handAngleLink": "",
  49 + "handAngleSwitch": false,
  50 + "likeEnable": 1,
  51 + "likesStyle": "thumb",
  52 + "liveExperience": 1,
  53 + "liveExperienceTime": 3,
  54 + "liveLandScape": "news",
  55 + "liveState": "end",
  56 + "liveStyle": 0,
  57 + "liveWay": 0,
  58 + "mlive": {
  59 + "barrageShowEnable": false,
  60 + "giftEnable": false,
  61 + "mliveId": 20000016257,
  62 + "roomId": "5381b934-cea8-4338-bd12-5bf70af43e0c"
  63 + },
  64 + "notice": "",
  65 + "openComment": 1,
  66 + "padImageUri": "",
  67 + "planStartTime": "2024-04-03 05:00:00",
  68 + "playbackSwitch": true,
  69 + "preCommentFlag": 1,
  70 + "previewType": 1,
  71 + "previewUrl": "",
  72 + "shareSwitch": "",
  73 + "startTime": "2024-04-03 05:03:23",
  74 + "tplId": 5,
  75 + "vlive": [
  76 + {
  77 + "coverImageUrl": "",
  78 + "definition": [],
  79 + "liveStreamManagerId": null,
  80 + "liveStreamType": 1,
  81 + "liveUrl": "https://plwbthird.live.weibo.com/alicdn/5018938748437049.m3u8",
  82 + "name": "线路1",
  83 + "replayUri": "http://mlive3.video.weibocdn.com/record/alicdn/5018726527666338/index.m3u8",
  84 + "serialNum": null,
  85 + "shiftEnable": false,
  86 + "showPad": false,
  87 + "type": "play",
  88 + "vliveId": 186728
  89 + }
  90 + ],
  91 + "vrType": 0
  92 + },
  93 + "menuShow": 1,
  94 + "newIntroduction": "眼前有山河,心中有家国!每年清明节前夕,宁夏固原市第二中学和固原市弘文中学会组织入学新生,一天之内徒步54公里从学校往返任山河烈士陵园,用这种方式缅怀烈士们,这份坚定与执着已经延续了29年。",
  95 + "newLinkObject": null,
  96 + "newsBodyTitle": "",
  97 + "newsContent": "",
  98 + "newsContentBak": "",
  99 + "newsDownTitle": "",
  100 + "newsId": 20000016229,
  101 + "newsLinkUrl": "",
  102 + "newsShortTitle": "",
  103 + "newsSource": "41",
  104 + "newsSourceName": "",
  105 + "newsSummary": "",
  106 + "newsTags": "",
  107 + "newsTitle": "徒步54公里的思政课,坚守29年的薪火传承",
  108 + "newsType": 2,
  109 + "oldNewsId": "7218507",
  110 + "openAudio": 0,
  111 + "openComment": null,
  112 + "openLikes": null,
  113 + "photoList": [],
  114 + "popUps": [],
  115 + "preCommentFlag": null,
  116 + "publishTime": "2024-04-01 19:44:00",
  117 + "reLInfo": {
  118 + "channelId": 2061,
  119 + "relId": "500005272745",
  120 + "relObjectId": 2061,
  121 + "relType": "1"
  122 + },
  123 + "readFlag": 0,
  124 + "recommendShow": null,
  125 + "rmhInfo": null,
  126 + "rmhPlatform": 0,
  127 + "sceneId": "",
  128 + "serials": null,
  129 + "shareInfo": {
  130 + "shareCoverUrl": "https://rmrbcmsonline.peopleapp.com/upload/image/202404/202404011944259539.png?x-oss-process=image/resize,w_400",
  131 + "shareOpen": 1,
  132 + "sharePosterCoverUrl": "https://rmrbcmsonline.peopleapp.com/upload/image/202404/rmrb_71671711971849.png?x-oss-process=image/resize,m_fill,h_450,w_800,limit_0/quality,q_90",
  133 + "sharePosterOpen": 1,
  134 + "shareSummary": "人民日报,有品质的新闻",
  135 + "shareTitle": "徒步54公里的思政课,坚守29年的薪火传承",
  136 + "shareUrl": "https://people.pdnews.cn/column/20000016229-500005272745"
  137 + },
  138 + "specialColumnId": null,
  139 + "specialColumnName": "",
  140 + "subSceneId": "",
  141 + "timeline": null,
  142 + "topicInfo": null,
  143 + "traceId": "",
  144 + "traceInfo": "",
  145 + "userInfo": null,
  146 + "videoInfo": [],
  147 + "viewCount": 0,
  148 + "visitorComment": 1,
  149 + "voteInfo": null
  150 + }
  151 + ],
  152 + "message": "Success",
  153 + "meta": null,
  154 + "requestId": "",
  155 + "success": true,
  156 + "timestamp": 1712807514322
  157 + }
  158 + */
  159 + liveInfo: LiveInfo
  160 + fullColumnImgUrls: Array<FullColumnImgUrls>
  161 + vlive: Array<Vlive>
  162 + newsTitle: string
  163 + newIntroduction: string
  164 +}
  165 +
  166 +export interface LiveInfo {
  167 + //直播新闻-直播状态 wait待开播running直播中end已结束cancel已取消paused暂停
  168 + liveState: string
  169 + //2024-04-12 15:00:00 直播开始时间
  170 + planStartTime: string
  171 +}
  172 +
  173 +export interface FullColumnImgUrls {
  174 + url: string
  175 +}
  176 +
  177 +export interface Vlive {
  178 + //拉流直播 url
  179 + liveUrl: string
  180 + //直播回看地址,多路直播录制文件URL
  181 + replayUri: string
  182 +}
@@ -7,6 +7,10 @@ @@ -7,6 +7,10 @@
7 "main": "Index.ets", 7 "main": "Index.ets",
8 "version": "1.0.0", 8 "version": "1.0.0",
9 "dependencies": { 9 "dependencies": {
10 - "wdComponent": "file:../../features/wdComponent" 10 + "wdComponent": "file:../../features/wdComponent",
  11 + "wdPlayer": "file:../../features/wdPlayer",
  12 + "wdNetwork": "file:../../commons/wdNetwork",
  13 + "wdKit": "file:../../commons/wdKit",
  14 + "wdBean": "file:../../features/wdBean"
11 } 15 }
12 } 16 }
  1 +import { LiveDetailsBean } from 'wdBean/Index';
  2 +import { LiveViewModel } from '../viewModel/LiveViewModel';
1 import { BottomComponent } from '../widgets/details/BottomComponent'; 3 import { BottomComponent } from '../widgets/details/BottomComponent';
2 import { TabComponent } from '../widgets/details/TabComponent'; 4 import { TabComponent } from '../widgets/details/TabComponent';
3 -import { TopPlayComponent } from '../widgets/details/video/TopPlayComponet';  
4 5
5 @Entry 6 @Entry
6 @Component 7 @Component
7 export struct DetailPlayLivePage { 8 export struct DetailPlayLivePage {
8 TAG: string = 'DetailPlayLivePage'; 9 TAG: string = 'DetailPlayLivePage';
9 - @State playUrl:string=''  
10 - aboutToAppear(): void { 10 + liveViewModel: LiveViewModel = new LiveViewModel()
  11 + @Provide liveDetailsBean: LiveDetailsBean = {} as LiveDetailsBean
11 12
  13 + aboutToAppear(): void {
  14 + this.getLiveDetails()
12 } 15 }
13 16
14 build() { 17 build() {
15 Column() { 18 Column() {
16 - TopPlayComponent({playUrl:this.playUrl}) 19 + // TopPlayComponent({playUrl:this.playUrl})
17 TabComponent() 20 TabComponent()
18 BottomComponent() 21 BottomComponent()
19 } 22 }
@@ -22,7 +25,21 @@ export struct DetailPlayLivePage { @@ -22,7 +25,21 @@ export struct DetailPlayLivePage {
22 } 25 }
23 26
24 onPageShow(): void { 27 onPageShow(): void {
25 - this.playUrl='https://rmrbcmsonline.peopleapp.com/upload/rmh/video/mp4/202404/1712667051b573b0f3a7a22375.mp4' 28 +
  29 + }
  30 +
  31 + getLiveDetails() {
  32 + this.liveViewModel.getLiveDetails('20000016333', '500005300349', '1')//2024-04-12 15:00:00
  33 + // this.liveViewModel.getLiveDetails('20000016229', '500005272745', '1')//2024-04-03 05:00:00
  34 + .then(
  35 + (data) => {
  36 + if (data.length > 0) {
  37 + this.liveDetailsBean = data[0]
  38 + }
  39 + },
  40 + () => {
  41 +
  42 + })
26 } 43 }
27 44
28 aboutToDisappear(): void { 45 aboutToDisappear(): void {
  1 +import HashMap from '@ohos.util.HashMap';
  2 +import { HttpUrlUtils, ResponseDTO } from 'wdNetwork';
  3 +import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest';
  4 +import { Logger } from 'wdKit';
  5 +import { LiveDetailsBean } from 'wdBean/Index';
  6 +
  7 +const TAG = 'LiveModel'
  8 +
  9 +export class LiveModel {
  10 + /**
  11 + * 直播内容详情
  12 + * @param contentId
  13 + * @param relId 关系id
  14 + * @param relType 关系类型;1.频道关系;2.专题关系;
  15 + * @returns
  16 + */
  17 + getLiveDetails(contentId: string, relId: string, relType: string) {
  18 + let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
  19 + return new Promise<Array<LiveDetailsBean>>((success, fail) => {
  20 + HttpRequest.get<ResponseDTO<Array<LiveDetailsBean>>>(
  21 + HttpUrlUtils.getLiveDetailsUrl() + `?relId=${relId}&relType=${relType}&contentId=${contentId}`,
  22 + headers).then((data: ResponseDTO<Array<LiveDetailsBean>>) => {
  23 + if (!data || !data.data) {
  24 + fail("数据为空")
  25 + return
  26 + }
  27 + if (data.code != 0) {
  28 + fail(data.message)
  29 + return
  30 + }
  31 + success(data.data)
  32 + }, (error: Error) => {
  33 + fail(error.message)
  34 + Logger.debug(TAG + ":error ", error.toString())
  35 + })
  36 + })
  37 +
  38 + }
  39 +}
  40 +
  1 +import { LiveDetailsBean } from 'wdBean/Index'
  2 +import { LiveModel } from './LiveModel'
  3 +
  4 +const TAG = "LiveViewModel"
  5 +
  6 +export class LiveViewModel {
  7 + liveModel: LiveModel
  8 +
  9 + constructor() {
  10 + this.liveModel = new LiveModel()
  11 + }
  12 +
  13 + //直播详情
  14 + getLiveDetails(contentId: string, relId: string, relType: string) {
  15 + return new Promise<Array<LiveDetailsBean>>((success, fail) => {
  16 + this.liveModel.getLiveDetails(contentId, relId, relType).then((data) => {
  17 + success(data)
  18 + }).catch((message: string) => {
  19 + fail(message)
  20 + })
  21 + })
  22 +
  23 + }
  24 +}
1 import font from '@ohos.font' 1 import font from '@ohos.font'
  2 +import { LiveDetailsBean } from 'wdBean/Index'
  3 +import { DateTimeUtils, StringUtils } from 'wdKit/Index'
2 4
3 @Component 5 @Component
4 export struct LiveCountdownComponent { 6 export struct LiveCountdownComponent {
  7 + @Consume @Watch('calculateDataStatus') liveDetailsBean: LiveDetailsBean
5 textTimerController: TextTimerController = new TextTimerController() 8 textTimerController: TextTimerController = new TextTimerController()
6 @State format: string = 'HH:mm:ss' 9 @State format: string = 'HH:mm:ss'
  10 + @State month: string = ''
  11 + @State day: string = ''
  12 + @State hour: string = ''
  13 + @State minute: string = ''
  14 + @State differenceTimeStamp: number = 0
  15 + @State isCountDownStart: boolean = false
7 16
8 aboutToAppear(): void { 17 aboutToAppear(): void {
9 //注册字体 18 //注册字体
@@ -38,7 +47,7 @@ export struct LiveCountdownComponent { @@ -38,7 +47,7 @@ export struct LiveCountdownComponent {
38 47
39 @Builder 48 @Builder
40 showTitle() { 49 showTitle() {
41 - Text('距离直播开始还有') 50 + Text(this.isCountDownStart ? '距离直播开始还有' : '直播开始时间')
42 .maxLines(2) 51 .maxLines(2)
43 .textOverflow({ overflow: TextOverflow.Ellipsis }) 52 .textOverflow({ overflow: TextOverflow.Ellipsis })
44 .fontSize('14fp') 53 .fontSize('14fp')
@@ -49,18 +58,30 @@ export struct LiveCountdownComponent { @@ -49,18 +58,30 @@ export struct LiveCountdownComponent {
49 @Builder 58 @Builder
50 showCountDown() { 59 showCountDown() {
51 Row() { 60 Row() {
52 - this.showTimeStyle('10', true, 0)  
53 - this.showTimeStyle('月', false, 3)  
54 - this.showTimeStyle('8', true, 3)  
55 - this.showTimeStyle('日', false, 3)  
56 - this.showTimeStyle('16', true, 10)  
57 - this.showTimeStyle(':', true, 0)  
58 - this.showTimeStyle('05', true, 0) 61 + Text(this.month)
  62 + .showTimeStyleBold()
  63 + Text('月')
  64 + .showTimeStyleNormal()
  65 + .margin({ left: 3 })
  66 + Text(this.day)
  67 + .showTimeStyleBold()
  68 + .margin({ left: 3 })
  69 + Text('日')
  70 + .showTimeStyleNormal()
  71 + .margin({ left: 3 })
  72 + Text(this.hour)
  73 + .showTimeStyleBold()
  74 + .margin({ left: 10 })
  75 + Text(':')
  76 + .showTimeStyleBold()
  77 + Text(this.minute)
  78 + .showTimeStyleBold()
59 } 79 }
60 .margin({ top: 10 }) 80 .margin({ top: 10 })
61 - .visibility(Visibility.None) 81 + .visibility(this.isCountDownStart ? Visibility.None : Visibility.Visible
  82 + )
62 83
63 - TextTimer({ isCountDown: true, count: 24 * 60 * 60 * 1000 - 1000, controller: this.textTimerController }) 84 + TextTimer({ isCountDown: true, count: this.differenceTimeStamp, controller: this.textTimerController })
64 .format(this.format) 85 .format(this.format)
65 .fontSize('40fp') 86 .fontSize('40fp')
66 .fontWeight(FontWeight.Bold) 87 .fontWeight(FontWeight.Bold)
@@ -70,6 +91,7 @@ export struct LiveCountdownComponent { @@ -70,6 +91,7 @@ export struct LiveCountdownComponent {
70 console.info('textTimer notCountDown utc is:' + utc + ', elapsedTime: ' + elapsedTime) 91 console.info('textTimer notCountDown utc is:' + utc + ', elapsedTime: ' + elapsedTime)
71 }) 92 })
72 .margin({ top: 10 }) 93 .margin({ top: 10 })
  94 + .visibility(this.isCountDownStart ? Visibility.Visible : Visibility.None)
73 } 95 }
74 96
75 @Builder 97 @Builder
@@ -89,13 +111,37 @@ export struct LiveCountdownComponent { @@ -89,13 +111,37 @@ export struct LiveCountdownComponent {
89 // .backgroundColor('#CCCCCC') 111 // .backgroundColor('#CCCCCC')
90 } 112 }
91 113
92 - @Builder  
93 - showTimeStyle(value: string, isBold: boolean, left: number) {  
94 - Text(value)  
95 - .fontSize(isBold ? '40fp' : '16fp')  
96 - .fontFamily(isBold ? 'BebasNeue_Regular' : '')  
97 - .fontWeight(isBold ? FontWeight.Bold : 500)  
98 - .fontColor('#222222')  
99 - .margin({ left: left }) 114 + calculateDataStatus() {
  115 + let startTimeStamp: number = DateTimeUtils.getDateTimestamp(this.liveDetailsBean.liveInfo.planStartTime)
  116 + let currentTimeStamp: number = DateTimeUtils.getTimeStamp()
  117 + let _differenceTimeStampTmp = startTimeStamp - currentTimeStamp
  118 + this.isCountDownStart = _differenceTimeStampTmp <= 4 * 60 * 60 * 1000
  119 + if (this.isCountDownStart) {
  120 + this.differenceTimeStamp = _differenceTimeStampTmp
  121 + return
  122 + }
  123 + //2024-04-01 19:44:00-trim->2024-04-0119:44:00
  124 + if (StringUtils.isNotEmpty(this.liveDetailsBean.liveInfo.planStartTime)) {
  125 + let playStartTimeTmp = this.liveDetailsBean.liveInfo.planStartTime.trim()
  126 + this.month = playStartTimeTmp.substring(5, 7)
  127 + this.day = playStartTimeTmp.substring(8, 10)
  128 + this.hour = playStartTimeTmp.substring(11, 13)
  129 + this.minute = playStartTimeTmp.substring(14, 16)
  130 + }
100 } 131 }
  132 +}
  133 +
  134 +@Extend(Text)
  135 +function showTimeStyleNormal() {
  136 + .fontSize('16fp')
  137 + .fontWeight(500)
  138 + .fontColor('#222222')
  139 +}
  140 +
  141 +@Extend(Text)
  142 +function showTimeStyleBold() {
  143 + .fontSize('40fp')
  144 + .fontFamily('BebasNeue_Regular')
  145 + .fontWeight(FontWeight.Bold)
  146 + .fontColor('#222222')
101 } 147 }
  1 +import { LiveDetailsBean } from 'wdBean/Index'
1 import { TabChatComponent } from './TabChatComponent' 2 import { TabChatComponent } from './TabChatComponent'
2 import { TabInfoComponent } from './TabInfoComponent' 3 import { TabInfoComponent } from './TabInfoComponent'
3 import { TabLiveComponent } from './TabLiveComponent' 4 import { TabLiveComponent } from './TabLiveComponent'
  1 +import { LiveDetailsBean } from 'wdBean/Index'
1 import { LiveCountdownComponent } from './LiveCountdownComponent' 2 import { LiveCountdownComponent } from './LiveCountdownComponent'
2 3
3 @Component 4 @Component
4 export struct TabInfoComponent { 5 export struct TabInfoComponent {
  6 + @Consume liveDetailsBean: LiveDetailsBean
  7 +
5 aboutToAppear(): void { 8 aboutToAppear(): void {
6 } 9 }
7 10
@@ -23,7 +26,7 @@ export struct TabInfoComponent { @@ -23,7 +26,7 @@ export struct TabInfoComponent {
23 26
24 @Builder 27 @Builder
25 showLiveTitle() { 28 showLiveTitle() {
26 - Text('国新办发布会丨介绍防汛抗旱工国新办发布会丨介绍防汛抗旱工作情况国新办发布会丨介绍防汛抗旱工作情况作情况国新办发布会丨介绍防汛抗旱工作情况') 29 + Text(this.liveDetailsBean.newsTitle)
27 .maxLines(2) 30 .maxLines(2)
28 .textOverflow({ overflow: TextOverflow.Ellipsis }) 31 .textOverflow({ overflow: TextOverflow.Ellipsis })
29 .fontSize('18fp') 32 .fontSize('18fp')
@@ -33,7 +36,7 @@ export struct TabInfoComponent { @@ -33,7 +36,7 @@ export struct TabInfoComponent {
33 36
34 @Builder 37 @Builder
35 showLiveDetails() { 38 showLiveDetails() {
36 - Text('国务院新闻办公室将于7月25日上午10时举行国务院政策例行吹风会,请应急管理部副部长、水利部副部长王道席和自然资源部、水利部、应急管理部、中国气象局、国家消防救援局有关负责人介绍防汛抗旱工作情况,并答记者问。') 39 + Text(this.liveDetailsBean.newIntroduction)
37 .maxLines(5) 40 .maxLines(5)
38 .textOverflow({ overflow: TextOverflow.Ellipsis }) 41 .textOverflow({ overflow: TextOverflow.Ellipsis })
39 .fontSize('14fp') 42 .fontSize('14fp')