陈剑华

Merge remote-tracking branch 'origin/main'

@@ -11,6 +11,8 @@ import { TrackConstants, TrackingButton } from 'wdTracking'; @@ -11,6 +11,8 @@ import { TrackConstants, TrackingButton } from 'wdTracking';
11 import { faceDetector } from '@kit.CoreVisionKit'; 11 import { faceDetector } from '@kit.CoreVisionKit';
12 import mediaLibrary from '@ohos.multimedia.mediaLibrary'; 12 import mediaLibrary from '@ohos.multimedia.mediaLibrary';
13 import fileIo from '@ohos.file.fs'; 13 import fileIo from '@ohos.file.fs';
  14 +import { httpRequest } from '../utils/httpRequest';
  15 +import { taskpool } from '@kit.ArkTS';
14 16
15 const PERMISSIONS: Array<Permissions> = [ 17 const PERMISSIONS: Array<Permissions> = [
16 'ohos.permission.READ_IMAGEVIDEO', 18 'ohos.permission.READ_IMAGEVIDEO',
@@ -21,6 +23,23 @@ const PERMISSIONS: Array<Permissions> = [ @@ -21,6 +23,23 @@ const PERMISSIONS: Array<Permissions> = [
21 * saveButton参考文档 23 * saveButton参考文档
22 * https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/savebutton-0000001820999677 24 * https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/savebutton-0000001820999677
23 * */ 25 * */
  26 +@Concurrent
  27 +async function getPicture(imageUrl: string): Promise<ArrayBuffer> {
  28 + let ret: ArrayBuffer = await new Promise((resolve, reject) => {
  29 + httpRequest.httpRequestInStream(imageUrl, (res: ArrayBuffer) => {
  30 + resolve(res); // 成功时解析Promise
  31 + }, () => {
  32 + // 下载失败时弹窗提示检查网络
  33 + promptAction.showToast({
  34 + message: $r('app.string.image_request_fail'),
  35 + duration: 2000
  36 + });
  37 + reject(new Error('Image download failed')); // 失败时拒绝Promise
  38 + });
  39 + });
  40 + return ret
  41 +}
  42 +
24 @Component 43 @Component
25 export struct ImageDownloadComponent { 44 export struct ImageDownloadComponent {
26 @State image: PixelMap | undefined = undefined; 45 @State image: PixelMap | undefined = undefined;
@@ -79,61 +98,12 @@ export struct ImageDownloadComponent { @@ -79,61 +98,12 @@ export struct ImageDownloadComponent {
79 const context = getContext(this) as common.UIAbilityContext; 98 const context = getContext(this) as common.UIAbilityContext;
80 const atManager = abilityAccessCtrl.createAtManager(); 99 const atManager = abilityAccessCtrl.createAtManager();
81 await atManager.requestPermissionsFromUser(context, PERMISSIONS); 100 await atManager.requestPermissionsFromUser(context, PERMISSIONS);
82 - this.getPicture();  
83 - }  
84 -  
85 - /**  
86 - * 通过http的request方法从网络下载图片资源  
87 - */  
88 - async getPicture() {  
89 - // 每一个httpRequest对应一个HTTP请求任务,不可复用  
90 - let httpRequest = http.createHttp();  
91 - // 用于订阅HTTP响应头事件  
92 - httpRequest.on('headersReceive', (header: Object) => {  
93 - //console.info('header: ' + JSON.stringify(header));  
94 - });  
95 - // 用于订阅HTTP流式响应数据接收事件  
96 - let res = new ArrayBuffer(0);  
97 - httpRequest.on('dataReceive', (data: ArrayBuffer) => {  
98 - const newRes = new ArrayBuffer(res.byteLength + data.byteLength);  
99 - const resView = new Uint8Array(newRes);  
100 - resView.set(new Uint8Array(res));  
101 - resView.set(new Uint8Array(data), res.byteLength);  
102 - res = newRes;  
103 - // //console.info('dataReceive res length: ' + res.byteLength);  
104 - });  
105 - // 用于订阅HTTP流式响应数据接收完毕事件  
106 - httpRequest.on('dataEnd', () => {  
107 - this.transcodePixelMap(res);  
108 - // 判断网络获取到的资源是否为ArrayBuffer类型  
109 - //console.info(`dataEnd getPicture ${res}`)  
110 - if (res instanceof ArrayBuffer) {  
111 - //console.info(`dataEnd getPicture`)  
112 - this.imageBuffer = res as ArrayBuffer;  
113 - }  
114 - //console.info('No more data in response, data receive end');  
115 - });  
116 - httpRequest.requestInStream(this.url,  
117 - (error: BusinessError, data: number) => {  
118 - if (error) {  
119 - // 下载失败时弹窗提示检查网络,不执行后续逻辑  
120 - promptAction.showToast({  
121 - message: $r('app.string.image_request_fail'),  
122 - duration: 2000  
123 - })  
124 - //console.error(`http reqeust failed with. Code: ${error.code}, message: ${error.message}`);  
125 - return;  
126 - }  
127 - // 取消订阅HTTP响应头事件  
128 - httpRequest.off('headersReceive');  
129 - // 取消订阅HTTP流式响应数据接收事件  
130 - httpRequest.off('dataReceive');  
131 - // 取消订阅HTTP流式响应数据接收完毕事件  
132 - httpRequest.off('dataEnd');  
133 - // 当该请求使用完毕时,调用destroy方法主动销毁  
134 - httpRequest.destroy();  
135 - }  
136 - ) 101 + // 通过任务池(taskpool)从网络下载图片资源
  102 + taskpool.execute(getPicture, this.url).then((res) => {
  103 + const imgBuffer = res as ArrayBuffer
  104 + this.imageBuffer = imgBuffer;
  105 + this.transcodePixelMap(imgBuffer);
  106 + })
137 } 107 }
138 108
139 /** 109 /**
@@ -6,11 +6,29 @@ import { OffsetModel } from '../model/OffsetModel'; @@ -6,11 +6,29 @@ import { OffsetModel } from '../model/OffsetModel';
6 import { WindowSizeManager } from '../utils/Managers'; 6 import { WindowSizeManager } from '../utils/Managers';
7 import { runWithAnimation } from '../utils/FuncUtils'; 7 import { runWithAnimation } from '../utils/FuncUtils';
8 import { PhotoListBean } from 'wdBean/Index'; 8 import { PhotoListBean } from 'wdBean/Index';
9 -import { http } from '@kit.NetworkKit';  
10 import { router } from '@kit.ArkUI'; 9 import { router } from '@kit.ArkUI';
  10 +import { taskpool } from '@kit.ArkTS';
  11 +import { httpRequest } from '../utils/httpRequest';
11 12
12 const TAG = 'MultiPictureDetailItemComponent'; 13 const TAG = 'MultiPictureDetailItemComponent';
13 14
  15 +@Concurrent
  16 +async function getPicture(imageUrl: string): Promise<ArrayBuffer> {
  17 + let ret: ArrayBuffer = await new Promise((resolve, reject) => {
  18 + httpRequest.httpRequestInStream(imageUrl, (res: ArrayBuffer) => {
  19 + resolve(res); // 成功时解析Promise
  20 + }, () => {
  21 + // 下载失败时弹窗提示检查网络
  22 + promptAction.showToast({
  23 + message: $r('app.string.image_request_fail'),
  24 + duration: 2000
  25 + });
  26 + reject(new Error('Image download failed')); // 失败时拒绝Promise
  27 + });
  28 + });
  29 + return ret
  30 +}
  31 +
14 @Reusable 32 @Reusable
15 @Component 33 @Component
16 export struct MultiPictureDetailItemComponent { 34 export struct MultiPictureDetailItemComponent {
@@ -34,64 +52,16 @@ export struct MultiPictureDetailItemComponent { @@ -34,64 +52,16 @@ export struct MultiPictureDetailItemComponent {
34 windowSizeManager: WindowSizeManager = new WindowSizeManager(); 52 windowSizeManager: WindowSizeManager = new WindowSizeManager();
35 53
36 54
37 - async aboutToAppear() { 55 + aboutToAppear() {
38 this.imageUri = this.MultiPictureDetailItem.picPath 56 this.imageUri = this.MultiPictureDetailItem.picPath
39 - this.getPicture() 57 + // 通过任务池(taskpool)从网络下载图片资源
  58 + taskpool.execute(getPicture, this.imageUri).then((res) => {
  59 + const imgBuffer = res as ArrayBuffer
  60 + this.imageBuffer = imgBuffer;
  61 + this.transcodePixelMap(imgBuffer);
  62 + })
40 } 63 }
41 64
42 - /**  
43 - * 通过http的request方法从网络下载图片资源  
44 - */  
45 - async getPicture() {  
46 - // 每一个httpRequest对应一个HTTP请求任务,不可复用  
47 - let httpRequest = http.createHttp();  
48 - // 用于订阅HTTP响应头事件  
49 - httpRequest.on('headersReceive', (header: Object) => {  
50 - console.info('header: ' + JSON.stringify(header));  
51 - });  
52 - // 用于订阅HTTP流式响应数据接收事件  
53 - let res = new ArrayBuffer(0);  
54 - httpRequest.on('dataReceive', (data: ArrayBuffer) => {  
55 - const newRes = new ArrayBuffer(res.byteLength + data.byteLength);  
56 - const resView = new Uint8Array(newRes);  
57 - resView.set(new Uint8Array(res));  
58 - resView.set(new Uint8Array(data), res.byteLength);  
59 - res = newRes;  
60 - // console.info('dataReceive res length: ' + res.byteLength);  
61 - });  
62 - // 用于订阅HTTP流式响应数据接收完毕事件  
63 - httpRequest.on('dataEnd', () => {  
64 - this.transcodePixelMap(res);  
65 - // 判断网络获取到的资源是否为ArrayBuffer类型  
66 - console.info(`dataEnd getPicture ${res}`)  
67 - if (res instanceof ArrayBuffer) {  
68 - console.info(`dataEnd getPicture`)  
69 - this.imageBuffer = res as ArrayBuffer;  
70 - }  
71 - console.info('No more data in response, data receive end');  
72 - });  
73 - httpRequest.requestInStream(this.imageUri,  
74 - (error: BusinessError, data: number) => {  
75 - if (error) {  
76 - // 下载失败时弹窗提示检查网络,不执行后续逻辑  
77 - promptAction.showToast({  
78 - message: $r('app.string.image_request_fail'),  
79 - duration: 2000  
80 - })  
81 - console.error(`http reqeust failed with. Code: ${error.code}, message: ${error.message}`);  
82 - return;  
83 - }  
84 - // 取消订阅HTTP响应头事件  
85 - httpRequest.off('headersReceive');  
86 - // 取消订阅HTTP流式响应数据接收事件  
87 - httpRequest.off('dataReceive');  
88 - // 取消订阅HTTP流式响应数据接收完毕事件  
89 - httpRequest.off('dataEnd');  
90 - // 当该请求使用完毕时,调用destroy方法主动销毁  
91 - httpRequest.destroy();  
92 - }  
93 - )  
94 - }  
95 65
96 /** 66 /**
97 * 使用createPixelMap将ArrayBuffer类型的图片装换为PixelMap类型 67 * 使用createPixelMap将ArrayBuffer类型的图片装换为PixelMap类型
@@ -35,6 +35,8 @@ export class commentListModel extends PageModel { @@ -35,6 +35,8 @@ export class commentListModel extends PageModel {
35 totalCommentNum: string = '0' 35 totalCommentNum: string = '0'
36 hasNext: number = 0 36 hasNext: number = 0
37 list: commentItemModel[] = [] 37 list: commentItemModel[] = []
  38 + hotList: commentItemModel[] = [] // 仅第一页存在
  39 + hotIds: string[] = [] // 仅第一页存在
38 // constructor(pageNum:number, pageSize:number, totalCount: number, hasNext: number, list: commentItemModel[]) { 40 // constructor(pageNum:number, pageSize:number, totalCount: number, hasNext: number, list: commentItemModel[]) {
39 // super() 41 // super()
40 // this.pageNum = pageNum 42 // this.pageNum = pageNum
@@ -45,9 +47,16 @@ export class commentListModel extends PageModel { @@ -45,9 +47,16 @@ export class commentListModel extends PageModel {
45 // } 47 // }
46 } 48 }
47 49
  50 +export enum CommentItemCustomType {
  51 + comment = 0,
  52 + newTitle = 1, // 最新评论标题
  53 + hotTitle = 2, // 热门评论标题
  54 + hotComment = 3,
  55 +}
48 56
49 @Observed 57 @Observed
50 export class commentItemModel { 58 export class commentItemModel {
  59 + api_customType: CommentItemCustomType = CommentItemCustomType.comment
51 authorLike: string = '' 60 authorLike: string = ''
52 avatarFrame: string = '' 61 avatarFrame: string = ''
53 checkStatus: string = '' 62 checkStatus: string = ''
1 import { DateTimeUtils, EmitterEventId, EmitterUtils, LazyDataSource, 1 import { DateTimeUtils, EmitterEventId, EmitterUtils, LazyDataSource,
2 PublicDialogManager, 2 PublicDialogManager,
3 StringUtils } from 'wdKit/Index'; 3 StringUtils } from 'wdKit/Index';
4 -import { commentItemModel, WDPublicUserType } from '../model/CommentModel'; 4 +import { CommentItemCustomType, commentItemModel, WDPublicUserType } from '../model/CommentModel';
5 import commentViewModel from '../viewmodel/CommentViewModel'; 5 import commentViewModel from '../viewmodel/CommentViewModel';
6 import { CommentText } from './CommentText'; 6 import { CommentText } from './CommentText';
7 import { CommentCustomDialog } from './CommentCustomDialog'; 7 import { CommentCustomDialog } from './CommentCustomDialog';
@@ -37,6 +37,7 @@ export struct CommentComponent { @@ -37,6 +37,7 @@ export struct CommentComponent {
37 listScroller: ListScroller = new ListScroller(); // scroller控制器 37 listScroller: ListScroller = new ListScroller(); // scroller控制器
38 historyOffset: number = 0; // 上次浏览到列表距离顶端的偏移量offset 38 historyOffset: number = 0; // 上次浏览到列表距离顶端的偏移量offset
39 @State allDatas: LazyDataSource<commentItemModel> = new LazyDataSource(); 39 @State allDatas: LazyDataSource<commentItemModel> = new LazyDataSource();
  40 + firstPageHotIds: string = ''
40 @State dialogController: CustomDialogController | null = null; 41 @State dialogController: CustomDialogController | null = null;
41 // @State private browSingModel: commentListModel = new commentListModel() 42 // @State private browSingModel: commentListModel = new commentListModel()
42 43
@@ -49,6 +50,7 @@ export struct CommentComponent { @@ -49,6 +50,7 @@ export struct CommentComponent {
49 // 是否在弹框中 50 // 是否在弹框中
50 @Provide inDialog: boolean = false 51 @Provide inDialog: boolean = false
51 private dialogBeforeJumpOtherPageAction: () => void = () => {} 52 private dialogBeforeJumpOtherPageAction: () => void = () => {}
  53 + private dialogUpdateTitle: (title: string) => void = () => {}
52 54
53 // 在自定义组件即将析构销毁时将dialogControlle置空 55 // 在自定义组件即将析构销毁时将dialogControlle置空
54 aboutToDisappear() { 56 aboutToDisappear() {
@@ -99,7 +101,16 @@ export struct CommentComponent { @@ -99,7 +101,16 @@ export struct CommentComponent {
99 /*一级评论*/ 101 /*一级评论*/
100 // if (this.publishCommentModel.lastCommentModel) 102 // if (this.publishCommentModel.lastCommentModel)
101 if (this.publishCommentModel.lastCommentModel.parentId == '-1') { 103 if (this.publishCommentModel.lastCommentModel.parentId == '-1') {
102 - this.allDatas.addFirstItem(model) 104 +
  105 + let newCommentHeaderIndex = this.indexOfNewCommentHeaderTitle()
  106 + if (newCommentHeaderIndex === -1) {
  107 + let newCommentTitle = new commentItemModel()
  108 + newCommentTitle.api_customType = CommentItemCustomType.newTitle
  109 + this.allDatas.addItems([newCommentTitle, model])
  110 + } else {
  111 + this.allDatas.addItem(model, newCommentHeaderIndex + 1)
  112 + }
  113 +
103 } else { 114 } else {
104 //二级评论 115 //二级评论
105 this.allDatas.getDataArray().forEach(element => { 116 this.allDatas.getDataArray().forEach(element => {
@@ -116,41 +127,41 @@ export struct CommentComponent { @@ -116,41 +127,41 @@ export struct CommentComponent {
116 127
117 /*标题:全部评论*/ 128 /*标题:全部评论*/
118 @Builder 129 @Builder
119 - titleHeader() { 130 + titleHeader(title: string = "最新评论", showGapLine: boolean = false) {
120 131
121 - Row() {  
122 - Row() {  
123 - Image($r('app.media.redLine'))  
124 - .height(16)  
125 - .width(3)  
126 - Text('全部评论')  
127 - .fontSize(16)// .fontColor('#222222')  
128 - .fontColor($r('app.color.color_222222'))  
129 - .fontWeight(FontWeight.Medium)  
130 - .margin({ left: 5 }) 132 + Column() {
  133 + if (showGapLine) {
  134 + Divider().strokeWidth(6).color('#f5f5f5')
131 } 135 }
132 - .margin({ left: 16 })  
133 136
  137 + Row() {
  138 + Row() {
  139 + Image($r('app.media.redLine'))
  140 + .height(16)
  141 + .width(3)
  142 + Text(title)
  143 + .fontSize(16)// .fontColor('#222222')
  144 + .fontColor($r('app.color.color_222222'))
  145 + .fontWeight(FontWeight.Medium)
  146 + .margin({ left: 5 })
  147 + }
  148 + .margin({ left: 16 })
134 149
135 - Image($r('app.media.comment_close'))  
136 - .height(18)  
137 - .width(18)  
138 - .margin({ right: 16 })  
139 - .visibility(this.showCloseIcon ? Visibility.Visible : Visibility.Hidden)  
140 - .onClick(() => {  
141 - this.onCloseClick()  
142 - })  
143 150
144 - }  
145 - .height(44)  
146 - .width('100%')  
147 - .justifyContent(FlexAlign.SpaceBetween)  
148 - .onClick(() => {  
149 - // this.allDatas.push(new commentItemModel())  
150 - // this.allDatas.addFirstItem(new commentItemModel())  
151 - // this.allDatas.reloadData();  
152 - }) 151 + Image($r('app.media.comment_close'))
  152 + .height(18)
  153 + .width(18)
  154 + .margin({ right: 16 })
  155 + .visibility(this.showCloseIcon ? Visibility.Visible : Visibility.Hidden)
  156 + .onClick(() => {
  157 + this.onCloseClick()
  158 + })
153 159
  160 + }
  161 + .height(44)
  162 + .width('100%')
  163 + .justifyContent(FlexAlign.SpaceBetween)
  164 + }
154 } 165 }
155 166
156 /*1级评论作为titleHeader*/ 167 /*1级评论作为titleHeader*/
@@ -177,16 +188,29 @@ export struct CommentComponent { @@ -177,16 +188,29 @@ export struct CommentComponent {
177 build() { 188 build() {
178 Column() { 189 Column() {
179 List({ scroller: this.listScroller }) { 190 List({ scroller: this.listScroller }) {
180 - if (this.showTitleComponent) {  
181 - ListItemGroup({ header: this.titleHeader() })  
182 - }  
183 -  
184 if (!this.isComments) { 191 if (!this.isComments) {
  192 + if (this.showTitleComponent) {
  193 + ListItemGroup({ header: this.titleHeader() })
  194 + }
185 EmptyComponent({ emptyType: 17 }) 195 EmptyComponent({ emptyType: 17 })
186 .height(300) 196 .height(300)
187 } else { 197 } else {
188 LazyForEach(this.allDatas, (item: commentItemModel, index: number) => { 198 LazyForEach(this.allDatas, (item: commentItemModel, index: number) => {
189 - if (item.hasMore) { 199 + if (item.api_customType === CommentItemCustomType.newTitle) {
  200 + if (this.inDialog && index === 0) {
  201 + ListItemGroup()
  202 + } else {
  203 + ListItemGroup({ header: this.titleHeader("最新评论", index !== 0) })
  204 + }
  205 + }
  206 + else if (item.api_customType === CommentItemCustomType.hotTitle) {
  207 + if (this.inDialog && index === 0) {
  208 + ListItemGroup()
  209 + } else {
  210 + ListItemGroup({ header: this.titleHeader("热门评论") })
  211 + }
  212 + }
  213 + else if (item.hasMore) {
190 ListItemGroup({ 214 ListItemGroup({
191 header: this.CommentHeaderItem(item, index), 215 header: this.CommentHeaderItem(item, index),
192 footer: this.GroupFooterView(item, index) 216 footer: this.GroupFooterView(item, index)
@@ -234,6 +258,9 @@ export struct CommentComponent { @@ -234,6 +258,9 @@ export struct CommentComponent {
234 } 258 }
235 .scrollBar(BarState.Off) 259 .scrollBar(BarState.Off)
236 .margin({ bottom: 10 }) 260 .margin({ bottom: 10 })
  261 + .onScrollIndex((start) => {
  262 + this.updateDialogTitleWithStartIndex(start)
  263 + })
237 .onReachEnd(() => { 264 .onReachEnd(() => {
238 if (!this.fixedHeightMode) { 265 if (!this.fixedHeightMode) {
239 return 266 return
@@ -252,6 +279,30 @@ export struct CommentComponent { @@ -252,6 +279,30 @@ export struct CommentComponent {
252 279
253 } 280 }
254 281
  282 + updateDialogTitleWithStartIndex(start: number) {
  283 + if (!this.inDialog) {
  284 + return
  285 + }
  286 + if (this.allDatas.size() === 0) {
  287 + this.dialogUpdateTitle("评论")
  288 + return
  289 + }
  290 + if (this.allDatas.getFirst().api_customType !== CommentItemCustomType.hotTitle) {
  291 + this.dialogUpdateTitle("最新评论")
  292 + return
  293 + }
  294 + let newCommentHeaderIndex = this.indexOfNewCommentHeaderTitle()
  295 + if (newCommentHeaderIndex == -1) {
  296 + this.dialogUpdateTitle("热门评论")
  297 + return
  298 + }
  299 + if (start < newCommentHeaderIndex + 1) {
  300 + this.dialogUpdateTitle("热门评论")
  301 + } else {
  302 + this.dialogUpdateTitle("最新评论")
  303 + }
  304 + }
  305 +
255 parentOnReachEnd() { 306 parentOnReachEnd() {
256 if (this.fixedHeightMode) { 307 if (this.fixedHeightMode) {
257 return 308 return
@@ -268,11 +319,41 @@ export struct CommentComponent { @@ -268,11 +319,41 @@ export struct CommentComponent {
268 //获取数据 319 //获取数据
269 async getData() { 320 async getData() {
270 let pageIndex = this.currentPage 321 let pageIndex = this.currentPage
271 - commentViewModel.fetchContentCommentList(pageIndex + '', this.publishCommentModel.targetId,  
272 - this.publishCommentModel.targetType) 322 + commentViewModel.fetchContentCommentList(pageIndex + ''
  323 + ,this.publishCommentModel.targetId
  324 + ,this.publishCommentModel.targetType
  325 + ,this.firstPageHotIds)
273 .then(commentListModel => { 326 .then(commentListModel => {
274 console.log('评论:', JSON.stringify(commentListModel.list)) 327 console.log('评论:', JSON.stringify(commentListModel.list))
275 328
  329 + if (pageIndex == 1) {
  330 + // 保存第一页热门评论ids
  331 + if (commentListModel.hotIds.length > 0) {
  332 + this.firstPageHotIds = commentListModel.hotIds.join(",")
  333 + }
  334 + if (commentListModel.list.length > 0) { // 热门评论增加头部
  335 + let newCommentTitle = new commentItemModel()
  336 + newCommentTitle.api_customType = CommentItemCustomType.newTitle
  337 + let newArray = [newCommentTitle]
  338 + commentListModel.list = newArray.concat(commentListModel.list)
  339 + }
  340 + if (commentListModel.hotList.length > 0) { // 最新评论增加头部
  341 + let hotCommentTitle = new commentItemModel()
  342 + hotCommentTitle.api_customType = CommentItemCustomType.hotTitle
  343 + let newArray = [hotCommentTitle]
  344 + commentListModel.hotList = newArray.concat(commentListModel.hotList)
  345 +
  346 + commentListModel.list = commentListModel.hotList.concat(commentListModel.list)
  347 + }
  348 + } else { // 非首页数据
  349 + if (commentListModel.list.length > 0 && !this.hasNewCommentHeaderTitle()) { // 如果之前仅存在热门评论,这里需要补下数据
  350 + let newCommentTitle = new commentItemModel()
  351 + newCommentTitle.api_customType = CommentItemCustomType.newTitle
  352 + let newArray = [newCommentTitle]
  353 + commentListModel.list = newArray.concat(commentListModel.list)
  354 + }
  355 + }
  356 +
276 // 这里需要先赋值,否则下次UI刷新可能重复进入第1页两次 357 // 这里需要先赋值,否则下次UI刷新可能重复进入第1页两次
277 this.currentPage = pageIndex + 1 358 this.currentPage = pageIndex + 1
278 359
@@ -313,6 +394,29 @@ export struct CommentComponent { @@ -313,6 +394,29 @@ export struct CommentComponent {
313 } 394 }
314 }) 395 })
315 } 396 }
  397 +
  398 + hasNewCommentHeaderTitle() {
  399 + let hasNewCommentHeader = false
  400 + this.allDatas.getDataArray().forEach((comment) => {
  401 + if (comment.api_customType === CommentItemCustomType.newTitle) {
  402 + hasNewCommentHeader = true
  403 + }
  404 + })
  405 + return hasNewCommentHeader
  406 + }
  407 +
  408 + indexOfNewCommentHeaderTitle() {
  409 + let resultIndex = -1;
  410 + const array = this.allDatas.getDataArray()
  411 + for (let index = 0; index < array.length; index++) {
  412 + if (array[index].api_customType === CommentItemCustomType.newTitle) {
  413 + resultIndex = index;
  414 + break
  415 + }
  416 + }
  417 + return resultIndex
  418 + }
  419 +
316 } 420 }
317 421
318 422
@@ -74,6 +74,7 @@ struct CommentListDialog { @@ -74,6 +74,7 @@ struct CommentListDialog {
74 /// 内部使用 74 /// 内部使用
75 @Link publishCommentModel: publishCommentModel 75 @Link publishCommentModel: publishCommentModel
76 @State private operationButtonList: string[] = [] 76 @State private operationButtonList: string[] = []
  77 + @State title: string = "评论"
77 78
78 /// 外部初始化 79 /// 外部初始化
79 @Link contentDetailData: ContentDetailDTO // 详情页传 80 @Link contentDetailData: ContentDetailDTO // 详情页传
@@ -122,6 +123,9 @@ struct CommentListDialog { @@ -122,6 +123,9 @@ struct CommentListDialog {
122 if (this.onClose) { 123 if (this.onClose) {
123 this.onClose() 124 this.onClose()
124 } 125 }
  126 + },
  127 + dialogUpdateTitle: (title) => {
  128 + this.title = title
125 } 129 }
126 }).layoutWeight(1) 130 }).layoutWeight(1)
127 131
@@ -153,7 +157,7 @@ struct CommentListDialog { @@ -153,7 +157,7 @@ struct CommentListDialog {
153 Image($r('app.media.redLine')) 157 Image($r('app.media.redLine'))
154 .height(16) 158 .height(16)
155 .width(3) 159 .width(3)
156 - Text('全部评论') 160 + Text(this.title)
157 .fontSize(16)// .fontColor('#222222') 161 .fontSize(16)// .fontColor('#222222')
158 .fontColor($r('app.color.color_222222')) 162 .fontColor($r('app.color.color_222222'))
159 .fontWeight(FontWeight.Medium) 163 .fontWeight(FontWeight.Medium)
@@ -8,6 +8,7 @@ import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest'; @@ -8,6 +8,7 @@ import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest';
8 import { ProcessUtils, WDRouterPage, WDRouterRule } from 'wdRouter/Index'; 8 import { ProcessUtils, WDRouterPage, WDRouterRule } from 'wdRouter/Index';
9 import { TrackConstants, TrackingContent } from 'wdTracking/Index'; 9 import { TrackConstants, TrackingContent } from 'wdTracking/Index';
10 import { 10 import {
  11 + CommentItemCustomType,
11 commentItemModel, 12 commentItemModel,
12 commentListModel, 13 commentListModel,
13 commentStatusListModel, 14 commentStatusListModel,
@@ -33,9 +34,13 @@ class CommentViewModel { @@ -33,9 +34,13 @@ class CommentViewModel {
33 } 34 }
34 35
35 /*获取所有评论*/ 36 /*获取所有评论*/
36 - fetchContentCommentList(pageNum: string, contentId: string, contentType: string): Promise<commentListModel> { 37 + fetchContentCommentList(pageNum: string, contentId: string, contentType: string, firstPageHotIds: string = ''): Promise<commentListModel> {
37 38
38 let url = HttpUrlUtils.getContentCommentListDataUrl() + `?&pageSize=${10}&pageNum=${pageNum}&contentId=${contentId}&contentType=${contentType}&deviceId=${HttpUtils.getDeviceId()}&userId=${HttpUtils.getUserId()}&userType=${HttpUtils.getUserType()}&time=${DateTimeUtils.getCurTime(DateTimeUtils.PATTERN_DATE_TIME_HYPHEN)}` 39 let url = HttpUrlUtils.getContentCommentListDataUrl() + `?&pageSize=${10}&pageNum=${pageNum}&contentId=${contentId}&contentType=${contentType}&deviceId=${HttpUtils.getDeviceId()}&userId=${HttpUtils.getUserId()}&userType=${HttpUtils.getUserType()}&time=${DateTimeUtils.getCurTime(DateTimeUtils.PATTERN_DATE_TIME_HYPHEN)}`
  40 + url = url + "&hotComment=1"
  41 + if (pageNum !== "1" && firstPageHotIds.length > 0) {
  42 + url = url + `&hotIds=` + firstPageHotIds
  43 + }
39 url = url.replace(' ', '%20') 44 url = url.replace(' ', '%20')
40 45
41 return new Promise<commentListModel>((success, fail) => { 46 return new Promise<commentListModel>((success, fail) => {
@@ -49,9 +54,23 @@ class CommentViewModel { @@ -49,9 +54,23 @@ class CommentViewModel {
49 return 54 return
50 } 55 }
51 let listData = data.data as commentListModel 56 let listData = data.data as commentListModel
52 - this.fetchCommentStatusAndConfigAuthIcon(listData).then((commentListModel) => { 57 + this.fetchCommentStatusAndConfigAuthIcon(listData, false).then((commentListModel) => {
53 console.log(TAG, 'fetchCommentStatusAndConfigAuthIcon完成') 58 console.log(TAG, 'fetchCommentStatusAndConfigAuthIcon完成')
54 - success(commentListModel) 59 +
  60 + if (pageNum !== "1") {
  61 + success(commentListModel)
  62 + return
  63 + }
  64 +
  65 + // 热门评论批查
  66 + this.fetchCommentStatusAndConfigAuthIcon(listData, true).then((commentListModel) => {
  67 + console.log(TAG, 'hot comment fetchCommentStatusAndConfigAuthIcon完成')
  68 +
  69 + listData.hotList.forEach((item) => {
  70 + item.api_customType = CommentItemCustomType.hotComment
  71 + })
  72 + success(commentListModel)
  73 + })
55 }) 74 })
56 75
57 }, (error: Error) => { 76 }, (error: Error) => {
@@ -213,7 +232,7 @@ class CommentViewModel { @@ -213,7 +232,7 @@ class CommentViewModel {
213 } 232 }
214 233
215 /*多接口批查*/ 234 /*多接口批查*/
216 - fetchCommentStatusAndConfigAuthIcon(model: commentListModel): Promise<commentListModel> { 235 + fetchCommentStatusAndConfigAuthIcon(model: commentListModel, hot: boolean = false): Promise<commentListModel> {
217 236
218 let commentIDs: string[] = []; 237 let commentIDs: string[] = [];
219 238
@@ -222,7 +241,7 @@ class CommentViewModel { @@ -222,7 +241,7 @@ class CommentViewModel {
222 let creatorIDs: string[] = []; 241 let creatorIDs: string[] = [];
223 242
224 //主评论 243 //主评论
225 - for (const element of model.list) { 244 + for (const element of (hot ? model.hotList : model.list)) {
226 if ((element.id + '').length > 0) { 245 if ((element.id + '').length > 0) {
227 commentIDs.push(element.id + '') 246 commentIDs.push(element.id + '')
228 } 247 }
@@ -277,7 +296,7 @@ class CommentViewModel { @@ -277,7 +296,7 @@ class CommentViewModel {
277 let listData = data.data as commentStatusModel[] 296 let listData = data.data as commentStatusModel[]
278 //点赞 297 //点赞
279 for (const element of listData) { 298 for (const element of listData) {
280 - for (const commentModel of model.list) { 299 + for (const commentModel of (hot ? model.hotList : model.list)) {
281 if (element.commentId == commentModel.id) { 300 if (element.commentId == commentModel.id) {
282 commentModel.api_status = element.status 301 commentModel.api_status = element.status
283 } 302 }
@@ -319,7 +338,7 @@ class CommentViewModel { @@ -319,7 +338,7 @@ class CommentViewModel {
319 let listData = data.data as commentStatusModel[] 338 let listData = data.data as commentStatusModel[]
320 339
321 for (const element of listData) { 340 for (const element of listData) {
322 - for (const commentModel of model.list) { 341 + for (const commentModel of (hot ? model.hotList : model.list)) {
323 if (element.userId == commentModel.fromUserId) { 342 if (element.userId == commentModel.fromUserId) {
324 commentModel.api_levelHead = element.levelHead 343 commentModel.api_levelHead = element.levelHead
325 } 344 }
@@ -364,7 +383,7 @@ class CommentViewModel { @@ -364,7 +383,7 @@ class CommentViewModel {
364 let listData = data.data as commentStatusModel[] 383 let listData = data.data as commentStatusModel[]
365 384
366 for (const element of listData) { 385 for (const element of listData) {
367 - for (const commentModel of model.list) { 386 + for (const commentModel of (hot ? model.hotList : model.list)) {
368 if (element.creatorId == commentModel.fromCreatorId) { 387 if (element.creatorId == commentModel.fromCreatorId) {
369 commentModel.api_authIcon = element.authIcon 388 commentModel.api_authIcon = element.authIcon
370 } 389 }
@@ -400,6 +419,7 @@ class CommentViewModel { @@ -400,6 +419,7 @@ class CommentViewModel {
400 deepCopyCommentItemModel(model: commentItemModel) { 419 deepCopyCommentItemModel(model: commentItemModel) {
401 let newModel = new commentItemModel() 420 let newModel = new commentItemModel()
402 421
  422 + newModel.api_customType = model.api_customType
403 newModel.authorLike = model.authorLike 423 newModel.authorLike = model.authorLike
404 newModel.avatarFrame = model.avatarFrame 424 newModel.avatarFrame = model.avatarFrame
405 newModel.checkStatus = model.checkStatus 425 newModel.checkStatus = model.checkStatus
  1 +import { http } from '@kit.NetworkKit';
  2 +
  3 +const TAG = 'httpRequestInStream'
  4 +export class httpRequest {
  5 + // 大于5M的下载请求,使用流下载
  6 + /**
  7 + * 发起HTTP请求以下载图片资源
  8 + * @param {string} imageUrl 图片的URL
  9 + * @param {Function} onSuccess 成功回调函数
  10 + * @param {Function} onError 失败回调函数
  11 + */
  12 + public static httpRequestInStream(imageUrl:string, onSuccess:Function, onError:Function) {
  13 + // 每一个httpRequest对应一个HTTP请求任务,不可复用
  14 + const httpRequest = http.createHttp();
  15 +
  16 + // 订阅HTTP响应头事件
  17 + httpRequest.on('headersReceive', (header) => {
  18 + // console.info('header: ' + JSON.stringify(header));
  19 + });
  20 +
  21 + // 用于订阅HTTP流式响应数据接收事件
  22 + let res = new ArrayBuffer(0);
  23 +
  24 + httpRequest.on('dataReceive', (data) => {
  25 + const newRes = new ArrayBuffer(res.byteLength + data.byteLength);
  26 + const resView = new Uint8Array(newRes);
  27 + resView.set(new Uint8Array(res));
  28 + resView.set(new Uint8Array(data), res.byteLength);
  29 + res = newRes;
  30 + // console.info(TAG, 'dataReceive res length: ' + res.byteLength);
  31 + });
  32 +
  33 + // 用于订阅HTTP流式响应数据接收完毕事件
  34 + httpRequest.on('dataEnd', () => {
  35 + if (res instanceof ArrayBuffer) {
  36 + // 如果成功,调用onSuccess回调
  37 + // console.info(TAG, 'Success in response, data receive end');
  38 + onSuccess(res);
  39 + } else {
  40 + // 如果数据不是ArrayBuffer类型,可以在这里处理异常
  41 + // console.error(TAG, 'Unexpected data type:', res);
  42 + onError(new Error('Data is not an ArrayBuffer'));
  43 + }
  44 + // console.info(TAG, 'No more data in response, data receive end');
  45 + });
  46 +
  47 + httpRequest.requestInStream(imageUrl, (error, data) => {
  48 + if (error) {
  49 + // 如果发生错误,取消订阅事件并销毁请求对象
  50 + httpRequest.off('headersReceive');
  51 + httpRequest.off('dataReceive');
  52 + httpRequest.off('dataEnd');
  53 + httpRequest.destroy();
  54 + // console.error(`http request failed with. Code: ${error.code}, message: ${error.message}`);
  55 + // 调用onError回调
  56 + onError(error);
  57 + return;
  58 + }
  59 +
  60 + // 取消订阅事件
  61 + httpRequest.off('headersReceive');
  62 + httpRequest.off('dataReceive');
  63 + httpRequest.off('dataEnd');
  64 +
  65 + // 销毁请求对象
  66 + httpRequest.destroy();
  67 + });
  68 + }
  69 +}
@@ -94,7 +94,8 @@ export struct PlayerTitleView { @@ -94,7 +94,8 @@ export struct PlayerTitleView {
94 } 94 }
95 clipStr += strArr[i] 95 clipStr += strArr[i]
96 } 96 }
97 - console.log(TAG, 'clipStr:', clipStr) 97 + clipStr += this.isOverLines ? '...' : '';
  98 + console.log(TAG, 'clipStr2:', clipStr)
98 return clipStr 99 return clipStr
99 } 100 }
100 101
@@ -130,7 +131,8 @@ export struct PlayerTitleView { @@ -130,7 +131,8 @@ export struct PlayerTitleView {
130 } 131 }
131 clipStr += strArr[i] 132 clipStr += strArr[i]
132 } 133 }
133 - console.log(TAG, 'clipTitleText clipStr:', clipStr) 134 + console.log(TAG, 'clipTitleText clipStr1:', clipStr)
  135 + clipStr += this.isTitleOverLines ? '...' : '';
134 return clipStr 136 return clipStr
135 } 137 }
136 138
@@ -217,7 +219,54 @@ export struct PlayerTitleView { @@ -217,7 +219,54 @@ export struct PlayerTitleView {
217 // }) 219 // })
218 // } 220 // }
219 // } else { 221 // } else {
220 - if (this.summary && this.titleLines < 4) { 222 + this.detailDesc()
  223 + // }
  224 + Text(DateTimeUtils.formatDate(new Date(this.contentDetailData?.publishTime).getTime(),
  225 + DateTimeUtils.PATTERN_DATE_TIME_HYPHEN_MM))
  226 + .fontSize(12)
  227 + .fontColor(Color.White)
  228 + .opacity(0.7)
  229 + .lineHeight(16)
  230 + .fontWeight(400)
  231 + .fontFamily('PingFang SC-Regular')
  232 + .margin({ top: 8, bottom: 8 })
  233 +
  234 + }
  235 + .width(this.windowWidth - 150 + 'px')
  236 + .padding({ left: 16, right: 22 })
  237 + .alignItems(HorizontalAlign.Start)
  238 + .visibility(this.isOpenDetail || this.isDragging ? Visibility.None : Visibility.Visible)
  239 + }
  240 +
  241 + @Builder
  242 + detailDesc() {
  243 + /**
  244 + * 标题大于三行或存在简介显示查看详情按钮
  245 + */
  246 + // if (this.rmhPlatform == 1) {
  247 + // if (this.titleHeight > 200 || this.summary) {
  248 + // Text('查看详情 > ')
  249 + // .padding({
  250 + // left: 6,
  251 + // right: 6,
  252 + // top: 4,
  253 + // bottom: 4
  254 + // })
  255 + // .borderRadius(2)
  256 + // .backgroundColor('#99636363')
  257 + // .fontFamily('PingFang SC-Regular')
  258 + // .fontColor(Color.White)
  259 + // .fontSize(12)
  260 + // .lineHeight(14)
  261 + // .fontWeight(400)
  262 + // .onClick(() => {
  263 + // this.isOpenDetail = true
  264 + // this.dialogController?.open()
  265 + // })
  266 + // }
  267 + // } else {
  268 + if (this.summary && this.titleLines < 4) {
  269 + Stack({ alignContent: Alignment.TopStart }) {
221 Text() { 270 Text() {
222 Span(this.clipText(this.summary, 14, this.summaryLines, this.windowWidth - 150 - vp2px(50))) 271 Span(this.clipText(this.summary, 14, this.summaryLines, this.windowWidth - 150 - vp2px(50)))
223 .fontSize(14) 272 .fontSize(14)
@@ -229,9 +278,18 @@ export struct PlayerTitleView { @@ -229,9 +278,18 @@ export struct PlayerTitleView {
229 this.isOpenDetail = true 278 this.isOpenDetail = true
230 this.dialogController?.open() 279 this.dialogController?.open()
231 }) 280 })
232 - if (this.isOverLines) {  
233 - Span('... 全文')  
234 - .fontColor('#888888') 281 + }
  282 + // .position({ x: 0, y: 0 })
  283 + .padding({
  284 + left: 0,
  285 + right: 6,
  286 + top: 0,
  287 + bottom: 8
  288 + })
  289 + if (this.isOverLines) {
  290 + Text() {
  291 + Span('全文')
  292 + .fontColor('#99FFFFFF')
235 .fontWeight(400) 293 .fontWeight(400)
236 .fontFamily('PingFang SC-Regular') 294 .fontFamily('PingFang SC-Regular')
237 .fontSize(12) 295 .fontSize(12)
@@ -251,80 +309,94 @@ export struct PlayerTitleView { @@ -251,80 +309,94 @@ export struct PlayerTitleView {
251 this.isOpenDetail = true 309 this.isOpenDetail = true
252 this.dialogController?.open() 310 this.dialogController?.open()
253 }) 311 })
254 -  
255 } 312 }
  313 + .textAlign(TextAlign.Center)
  314 + // .width('100%')
  315 + .position({ x: '100%', y: this.summaryLines*23 }) //行高+margin
  316 + .translate({ x: '-100%', y: '-100%' })
  317 + // .markAnchor({ x: '100%', y: '100%' })
  318 + .padding({
  319 + left: 0,
  320 + right: 6,
  321 + top: 0,
  322 + bottom: 6
  323 + })
256 } 324 }
257 - .padding({  
258 - left: 0, //6  
259 - right: 6,  
260 - top: 0, //4  
261 - bottom: 8  
262 - })  
263 -  
264 } 325 }
265 - // }  
266 - Text(DateTimeUtils.formatDate(new Date(this.contentDetailData?.publishTime).getTime(),  
267 - DateTimeUtils.PATTERN_DATE_TIME_HYPHEN_MM))  
268 - .fontSize(12)  
269 - .fontColor(Color.White)  
270 - .opacity(0.7)  
271 - .lineHeight(16)  
272 - .fontWeight(400)  
273 - .fontFamily('PingFang SC-Regular')  
274 - .margin({ top: 8, bottom: 8 })  
275 - 326 + .width('100%')
276 } 327 }
277 - .width(this.windowWidth - 150 + 'px')  
278 - .padding({ left: 16, right: 22 })  
279 - .alignItems(HorizontalAlign.Start)  
280 - .visibility(this.isOpenDetail || this.isDragging ? Visibility.None : Visibility.Visible)  
281 } 328 }
282 329
  330 + /**
  331 + * 标题
  332 + * */
283 @Builder 333 @Builder
284 titleBuilder() { 334 titleBuilder() {
285 - Text() {  
286 - Span(this.clipTitleText(this.getTitle(), 16, 4, this.windowWidth - 234 - vp2px(50)))  
287 - .fontSize(16)  
288 - .fontColor(Color.White)  
289 - .lineHeight(22)  
290 - .fontWeight(600)  
291 - .fontFamily('PingFang SC-Regular')  
292 - if (this.isTitleOverLines) {  
293 - Span('... 全文') 335 + // Stack({ alignContent: this.isTitleOverLines ? Alignment.BottomEnd : Alignment.Start }) {
  336 + Stack({ alignContent: Alignment.TopStart }) {
  337 + Text() {
  338 + Span(this.clipTitleText(this.getTitle(), 16, 4, this.windowWidth - 234 - vp2px(50)))
  339 + .fontSize(16)
294 .fontColor(Color.White) 340 .fontColor(Color.White)
  341 + .lineHeight(22)
295 .fontWeight(600) 342 .fontWeight(600)
296 .fontFamily('PingFang SC-Regular') 343 .fontFamily('PingFang SC-Regular')
297 - .fontSize(12)  
298 - .onClick(() => {  
299 - this.isOpenDetail = true  
300 - this.dialogController?.open()  
301 - })  
302 - ImageSpan($r('app.media.comment_unfold_svg'))  
303 - .width(14)  
304 - .height(14)  
305 - .objectFit(ImageFit.Fill)  
306 - .verticalAlign(ImageSpanAlignment.BOTTOM)  
307 - .margin({ bottom: 1 })// .padding({  
308 - // bottom: 4  
309 - // })  
310 - .onClick(() => {  
311 - this.isOpenDetail = true  
312 - this.dialogController?.open()  
313 - })  
314 344
315 } 345 }
  346 + // .position({ x: 0, y: 0 })
  347 + .onAreaChange((oldArea: Area, newArea: Area) => {
  348 + //console.info(`cj2024 titleLines = ${newArea.height as number} line = ${(newArea.height as number) / 20}`)
  349 + this.titleLines = Math.floor((newArea.height as number) / 22) // 22是行高
  350 + //console.info(`cj2024 titleLines = ${this.titleLines}`)
  351 + this.updateSummaryLines()
  352 + })
  353 + .maxLines(4)
  354 + .textOverflow({ overflow: TextOverflow.Ellipsis })
  355 + .padding({
  356 + left: 0, //6
  357 + right: 6,
  358 + top: 0, //4
  359 + bottom: 8
  360 + })
  361 +
  362 + Text() {
  363 + if (this.isTitleOverLines) {
  364 + Span('全文')
  365 + .fontColor('#99FFFFFF')
  366 + .fontWeight(600)
  367 + .fontFamily('PingFang SC-Regular')
  368 + .fontSize(12)
  369 + .onClick(() => {
  370 + this.isOpenDetail = true
  371 + this.dialogController?.open()
  372 + })
  373 + ImageSpan($r('app.media.comment_unfold_svg'))
  374 + .width(14)
  375 + .height(14)
  376 + .objectFit(ImageFit.Fill)
  377 + .verticalAlign(ImageSpanAlignment.BOTTOM)
  378 + .margin({ bottom: 1 })// .padding({
  379 + // bottom: 4
  380 + // })
  381 + .onClick(() => {
  382 + this.isOpenDetail = true
  383 + this.dialogController?.open()
  384 + })
  385 + }
  386 + }
  387 + .textAlign(TextAlign.Center)
  388 + // .width('100%')
  389 + .position({ x: '100%', y: this.titleLines*23 }) //行高+margin
  390 + .translate({ x: '-100%', y: '-100%' })
  391 + // .position({ x: '100%', y: '100%' })
  392 + // .markAnchor({ x: '100%', y: '100%' })
  393 + .padding({
  394 + left: 0, //6
  395 + right: 6,
  396 + top: 0, //4
  397 + bottom: 8
  398 + })
316 } 399 }
317 - .onAreaChange((oldArea: Area, newArea: Area) => {  
318 - //console.info(`cj2024 titleLines = ${newArea.height as number} line = ${(newArea.height as number) / 20}`)  
319 - this.titleLines = Math.floor((newArea.height as number) / 22) // 22是行高  
320 - //console.info(`cj2024 titleLines = ${this.titleLines}`)  
321 - this.updateSummaryLines()  
322 - })  
323 - .padding({  
324 - left: 0, //6  
325 - right: 6,  
326 - top: 0, //4  
327 - bottom: 8  
328 - }) 400 + .width('100%')
329 } 401 }
330 } 402 }
1 -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><g transform="matrix(0,-1,1,0,-16,16)"><g><path d="M10.303455641479491,17.960819560243227L4.363755241479492,23.900515460243227Q4.324434578479492,23.939835460243224,4.324704443879492,23.995445460243225Q4.324434578479492,24.051055460243226,4.363755241479492,24.090375460243223L10.303455641479491,30.030075460243225Q10.342505641479491,30.069075460243226,10.397735641479493,30.069075460243226Q10.452965641479492,30.069075460243226,10.492015641479492,30.030075460243225L11.387685641479493,29.134375460243227Q11.406435641479492,29.115675460243224,11.416585641479493,29.091175460243225Q11.426735641479493,29.066675460243225,11.426735641479493,29.040075460243223Q11.426735641479493,29.013575460243224,11.416585641479493,28.989075460243225Q11.406435641479492,28.964575460243225,11.387685641479493,28.945875460243226L6.437285641479493,23.995445460243225L11.387685641479493,19.045045460243227Q11.406435641479492,19.026295460243226,11.416585641479493,19.001795460243226Q11.426735641479493,18.977295460243226,11.426735641479493,18.950765460243225Q11.426735641479493,18.924245460243224,11.416585641479493,18.899744460243227Q11.406435641479492,18.875241460243224,11.387685641479493,18.856488460243224L10.492015641479492,17.960819560243227Q10.452965641479492,17.921767119783226,10.397735641479493,17.921767119783226Q10.342505641479491,17.921767119783226,10.303455641479491,17.960819560243227Z" fill-rule="evenodd" fill="#888888" fill-opacity="1"/></g></g></svg>  
  1 +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><g transform="matrix(0,-1,1,0,-16,16)"><g><path d="M10.303455641479491,17.960819560243227L4.363755241479492,23.900515460243227Q4.324434578479492,23.939835460243224,4.324704443879492,23.995445460243225Q4.324434578479492,24.051055460243226,4.363755241479492,24.090375460243223L10.303455641479491,30.030075460243225Q10.342505641479491,30.069075460243226,10.397735641479493,30.069075460243226Q10.452965641479492,30.069075460243226,10.492015641479492,30.030075460243225L11.387685641479493,29.134375460243227Q11.406435641479492,29.115675460243224,11.416585641479493,29.091175460243225Q11.426735641479493,29.066675460243225,11.426735641479493,29.040075460243223Q11.426735641479493,29.013575460243224,11.416585641479493,28.989075460243225Q11.406435641479492,28.964575460243225,11.387685641479493,28.945875460243226L6.437285641479493,23.995445460243225L11.387685641479493,19.045045460243227Q11.406435641479492,19.026295460243226,11.416585641479493,19.001795460243226Q11.426735641479493,18.977295460243226,11.426735641479493,18.950765460243225Q11.426735641479493,18.924245460243224,11.416585641479493,18.899744460243227Q11.406435641479492,18.875241460243224,11.387685641479493,18.856488460243224L10.492015641479492,17.960819560243227Q10.452965641479492,17.921767119783226,10.397735641479493,17.921767119783226Q10.342505641479491,17.921767119783226,10.303455641479491,17.960819560243227Z" fill-rule="evenodd" fill="#FFFFFF" fill-opacity="0.6"/></g></g></svg>