move.tsx 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. import { View, Text, Image } from "@tarojs/components";
  2. import './move.scss'
  3. import { useDispatch, useSelector } from "react-redux";
  4. import { getScenario, getThemeColor } from "@/features/health/hooks/health_hooks";
  5. import { useEffect, useState } from "react";
  6. import Taro, { useDidShow } from "@tarojs/taro";
  7. import { checkStart, setResult } from "@/store/action_results";
  8. import RequestType, { thirdPartRequest } from "@/services/thirdPartRequest";
  9. import { setAuth } from "@/features/trackSomething/hooks/werun";
  10. import { useTranslation } from "react-i18next";
  11. import { getActiveMoves, getActiveMovesCurrent, uploadActiveMoves } from "@/services/health";
  12. import dayjs from "dayjs";
  13. import { TimeFormatter } from "@/utils/time_format";
  14. import { jumpPage } from "@/features/trackTimeDuration/hooks/Common";
  15. import { rpxToPx } from "@/utils/tools";
  16. import { IconMore, IconSit, IconWalk } from "@/components/basic/Icons";
  17. import NewHeader, { NewHeaderType } from "../components/new_header";
  18. import NewButton, { NewButtonType } from "../base/new_button";
  19. import { MainColorType } from "@/context/themes/color";
  20. import StatusIndicator, { StatusType } from "../base/status_indicator";
  21. import showActionSheet from "@/components/basic/ActionSheet";
  22. import ListFooter from "../components/list_footer";
  23. import StickyDateList from "../components/sticky_date_list";
  24. let timer
  25. let myScrollTop = 0
  26. export default function Move() {
  27. const health = useSelector((state: any) => state.health);
  28. const [allowRun, setAllowRun] = useState(false)
  29. const [loaded, setLoaded] = useState(false)
  30. const [loadAuth, setLoadAuth] = useState(false)
  31. const [count, setCount] = useState(0)
  32. const [data, setData] = useState<any>(null)
  33. const [list, setList] = useState<any>([])
  34. const [total, setTotal] = useState(0)
  35. const [index, setIndex] = useState(1)
  36. const [hideCurrentRecent, setHideCurrentRecent] = useState(false)
  37. const [moreActive, setMoreActive] = useState(false)
  38. const [hours, setHours] = useState<any>([])
  39. const [startDate, setStarteDate] = useState('')
  40. const [btnDisable,setBtnDisable] = useState(false)
  41. const [isPulling, setIsPulling] = useState(false)
  42. const [itemLayouts, setItemLayouts] = useState<any>([])
  43. const [showDate, setShowDate] = useState(false)
  44. const [date, setDate] = useState('')
  45. const [page, setPage] = useState(1)
  46. const [loading, setLoading] = useState(false)
  47. let navigation, showActionSheetWithOptions;
  48. const { t } = useTranslation()
  49. const dispatch = useDispatch()
  50. useEffect(() => {
  51. getMovesCurrent()
  52. getMovesHistory(1)
  53. checkAuth()
  54. timer = setInterval(() => {
  55. setCount((index) => index + 1)
  56. const date = new Date()
  57. if (date.getMinutes() == 10 && date.getSeconds() == 0) {
  58. getMovesCurrent()
  59. getMovesHistory(1)
  60. }
  61. }, 1000)
  62. return () => {
  63. clearInterval(timer)
  64. }
  65. }, [])
  66. useEffect(() => {
  67. if (list.length > 0) {
  68. setTimeout(() => {
  69. measureItemLayouts()
  70. }, 500)
  71. }
  72. }, [list])
  73. function measureItemLayouts() {
  74. const query = Taro.createSelectorQuery()
  75. list.forEach((item, index) => {
  76. query.select(`#history-${index}`).boundingClientRect()
  77. });
  78. query.exec((res) => {
  79. var layouts: any = []
  80. res.forEach((rect, index) => {
  81. if (rect) {
  82. layouts[index] = rect.top + myScrollTop
  83. }
  84. });
  85. setItemLayouts(layouts)
  86. })
  87. }
  88. function checkAuth() {
  89. Taro.getSetting({
  90. success: res => {
  91. //第一步,检测是否有授权 - 没有授权
  92. if (!res.authSetting['scope.werun']) {
  93. setAllowRun(false)
  94. Taro.setStorage({ key: 'auth', data: false })
  95. }
  96. else {
  97. setAllowRun(true)
  98. Taro.setStorage({ key: 'auth', data: true })
  99. }
  100. setLoadAuth(true)
  101. }
  102. })
  103. }
  104. useDidShow(() => {
  105. checkAuth()
  106. getMovesCurrent()
  107. getMovesHistory(1)
  108. })
  109. global.updateMove = () => {
  110. getMovesCurrent()
  111. getMovesHistory(1)
  112. }
  113. function getMovesCurrent() {
  114. getActiveMovesCurrent().then(res => {
  115. setLoaded(true)
  116. setData(res)
  117. setStarteDate((res as any).start_date)
  118. var array = (res as any).hours
  119. var temps: any = []
  120. for (var i = array.length - 1; i >= 0; i--) {
  121. var obj = array[i]
  122. if (obj.status != 'WFS') {
  123. temps.push(obj)
  124. }
  125. }
  126. setHours(temps)
  127. // setHours((res as any).hours.reverse())
  128. })
  129. }
  130. function getMovesHistory(page) {
  131. setIndex(page)
  132. // setIsPulling(true)
  133. setLoading(true)
  134. getActiveMoves({
  135. page: page,
  136. limit: 10,
  137. }
  138. ).then(res => {
  139. if (page == 1) {
  140. setList((res as any).data)
  141. setTotal((res as any).total)
  142. }
  143. else {
  144. setList([...list, ...(res as any).data])
  145. }
  146. setIsPulling(false)
  147. setLoading(false)
  148. }).catch(e => {
  149. setLoading(false)
  150. })
  151. }
  152. function more() {
  153. if (loading) return;
  154. if (total == list.length) return;
  155. var index = page;
  156. index++;
  157. setPage(index)
  158. getMovesHistory(index)
  159. }
  160. function onScroll(e) {
  161. var top = e.detail.scrollTop
  162. myScrollTop = top
  163. if (itemLayouts.length > 0) {
  164. var i = -1
  165. var date = ''
  166. list.forEach((item, index) => {
  167. if (top >= itemLayouts[index] - 50) {
  168. i = index
  169. var currentDate = (list[index].date + '').substring(0, 6)
  170. date = currentDate.substring(0, 4) + '年' + currentDate.substring(4, 6) + '月'
  171. }
  172. })
  173. setShowDate(i != -1)
  174. setDate(date)
  175. }
  176. else {
  177. setShowDate(false)
  178. setDate('')
  179. }
  180. }
  181. function tapLog() {
  182. // if (getStatus() != 'open') {
  183. // return
  184. // }
  185. if (isExtra()) {
  186. return;
  187. }
  188. if (allowRun) {
  189. checkout()
  190. }
  191. else {
  192. setAuth(successAuth, refuseAuth, t)
  193. }
  194. }
  195. function successAuth() {
  196. Taro.setStorage({ key: 'auth', data: true })
  197. setAllowRun(true)
  198. }
  199. function refuseAuth() {
  200. Taro.setStorage({ key: 'auth', data: false })
  201. setAllowRun(false)
  202. }
  203. function checkout() {
  204. dispatch(checkStart());
  205. getWeRunData(false)
  206. }
  207. function getWeRunData(autoCheck = false) {
  208. if (autoCheck) {
  209. return
  210. }
  211. else {
  212. dispatch(checkStart());
  213. }
  214. // setTitle('打卡');
  215. setAllowRun(true)
  216. let schedule_id = null
  217. const hour = currentHourPeriod()
  218. data.hours.map(item => {
  219. if (item.hour == hour) {
  220. schedule_id = item.schedule_id
  221. }
  222. })
  223. if (btnDisable) return
  224. setBtnDisable(true)
  225. thirdPartRequest(RequestType.RequestTypeWXRunData).then(res => {
  226. console.log(res)
  227. uploadActiveMoves({
  228. wechat_run: {
  229. encryptedData: (res as any).encryptedData,
  230. iv: (res as any).iv,
  231. },
  232. start_date: startDate,
  233. date: dayjs().format('YYYYMMDD'),
  234. hour: hour,
  235. timestamp: new Date().getTime(),
  236. schedule_id: schedule_id
  237. }).then(res => {
  238. if ((res as any).error_code == 'WX_STEP_PARSE_FAIL') {
  239. retry(hour, schedule_id)
  240. return
  241. }
  242. // Taro.showToast({
  243. // title: '上报成功',
  244. // icon: 'none'
  245. // })
  246. // console.log(res)
  247. // console.log('move result')
  248. jumpPage('./post_result?data=' + JSON.stringify((res as any).post_result))
  249. getMovesCurrent()
  250. getMovesHistory(1)
  251. setBtnDisable(false)
  252. }).catch(e=>{
  253. setBtnDisable(false)
  254. })
  255. }).catch(_ => {
  256. dispatch(setResult({ isSuccess: false }) as any)
  257. setBtnDisable(false)
  258. })
  259. }
  260. function retry(hour, schedule_id) {
  261. Taro.login().then(res0 => {
  262. thirdPartRequest(RequestType.RequestTypeWXRunData).then(res => {
  263. uploadActiveMoves({
  264. code: res0.code,
  265. wechat_run: {
  266. encryptedData: (res as any).encryptedData,
  267. iv: (res as any).iv,
  268. },
  269. start_date: startDate,
  270. date: dayjs().format('YYYYMMDD'),
  271. hour: hour,
  272. timestamp: new Date().getTime(),
  273. schedule_id: schedule_id
  274. }).then(res => {
  275. if ((res as any).error_code == 'WX_STEP_PARSE_FAIL') {
  276. Taro.showToast({
  277. title: '获取失败',
  278. icon: 'none'
  279. })
  280. return
  281. }
  282. jumpPage('./post_result?data=' + JSON.stringify((res as any).post_result))
  283. getMovesCurrent()
  284. getMovesHistory(1)
  285. })
  286. }).catch(_ => {
  287. dispatch(setResult({ isSuccess: false }) as any)
  288. })
  289. })
  290. }
  291. function currentCheckHour() {
  292. let currentHour = new Date().getHours()
  293. const minute = new Date().getMinutes()
  294. if (minute < 10) {
  295. currentHour -= 1;
  296. if (currentHour < 0) {
  297. currentHour = 23
  298. }
  299. }
  300. return currentHour
  301. }
  302. function currentHourChecked() {
  303. const currentHour = currentCheckHour()
  304. var isChecked = false
  305. data.hours.map((item) => {
  306. if (item.hour == currentHour && item.status != 'WFS') {
  307. isChecked = true
  308. }
  309. })
  310. return isChecked;
  311. }
  312. function getStatus() {
  313. const minute = new Date().getMinutes()
  314. if (minute >= 50 || minute < 10) {
  315. var isChecked = currentHourChecked()
  316. if (isChecked) {
  317. return 'upcoming'
  318. }
  319. return 'open'
  320. }
  321. return 'upcoming'
  322. }
  323. function getDuration() {
  324. let now = new Date()
  325. let pre = new Date().getTime()
  326. if (now.getMinutes() < 10) {
  327. pre -= 3600 * 1000
  328. }
  329. let next = pre + 3600 * 1000
  330. if (currentHourChecked()) {
  331. pre = next
  332. next = pre + 3600 * 1000
  333. }
  334. return dayjs(pre).format('HH:00') + '-' + dayjs(next).format('HH:00')
  335. }
  336. function getCurrentTarget() {
  337. let now = new Date()
  338. let pre = new Date().getTime()
  339. if (now.getMinutes() < 10) {
  340. pre -= 3600 * 1000
  341. }
  342. if (currentHourChecked()) {
  343. pre += 3600 * 1000
  344. }
  345. const hour = new Date(pre).getHours()
  346. for (var i = 0; i < data.hours.length; i++) {
  347. if (data.hours[i].hour == hour) {
  348. return `${data.hours[i].target_steps}`//`${data.hours[i].real_steps}/${data.hours[i].target_steps}`
  349. }
  350. }
  351. }
  352. function process() {
  353. return `${data.stat.active_hours} / ${data.stat.waking_hours}`
  354. }
  355. function currentHourPeriod() {
  356. const time = new Date()
  357. let hour = time.getHours()
  358. //假如00:09,则为23小时
  359. if (time.getMinutes() < 10) {
  360. hour--;
  361. }
  362. if (hour < 0) {
  363. hour = 23
  364. }
  365. return hour
  366. }
  367. function isExtra() {
  368. const hour = currentHourPeriod()
  369. var isFind = false
  370. data.hours.map((item) => {
  371. if (item.hour == hour) {
  372. isFind = true
  373. }
  374. })
  375. return !isFind
  376. }
  377. function currentHistory(item) {
  378. var start = item.hour
  379. var isYesterday = start > new Date().getHours()
  380. var end = start + 1
  381. start = (start + '').padStart(2, '0')
  382. end = (end + '').padStart(2, '0')
  383. return <View className="current_history_item">
  384. <View style={{ display: 'flex', flexDirection: 'column' }}>
  385. <Text className="h24" style={{ color: MainColorType.g02, lineHeight: rpxToPx(36) + 'px' }}>{start}:00-{end}:00</Text>
  386. {
  387. item.is_extra && <Text style={{ color: getThemeColor('SLEEP') }}>is Extra</Text>
  388. }
  389. <Text className="h24 bold"><Text className="h34 bold">{item.real_steps}</Text> / {item.target_steps} steps</Text>
  390. <View style={{
  391. height: rpxToPx(18), flexShrink: 0,
  392. }} />
  393. </View>
  394. {
  395. item.status == 'MISSED' && <Text className="missed">Missed</Text>
  396. }
  397. {
  398. item.status == 'SEDENTARY' && <IconSit width={rpxToPx(44)} color={MainColorType.g02} />
  399. }
  400. {
  401. item.status == 'ACTIVE' && <IconWalk width={rpxToPx(44)} color={MainColorType.active} />
  402. }
  403. <View className="border_footer_line" />
  404. </View>
  405. }
  406. function activeHour() {
  407. return <View className="summary">
  408. <View className="summary_header">
  409. <Text className="h34 bold">Last {hours.length} Hour{hours.length == 1 ? '' : 's'}</Text>
  410. <View className="border_footer_line" />
  411. </View>
  412. {
  413. !hideCurrentRecent && <View className="summary_content2">
  414. {
  415. hours.map((item, index) => {
  416. if (index >= 3 && !moreActive) return <View key={index} />
  417. return <View key={index}>
  418. {
  419. currentHistory(item)
  420. }
  421. </View>
  422. })
  423. }
  424. {
  425. hours.length > 3 && <View className="recent_btn_bg">
  426. <View style={{ height: rpxToPx(68), minWidth: rpxToPx(200), display: 'flex' }}>
  427. <NewButton
  428. onClick={() => {
  429. setMoreActive(!moreActive)
  430. }}
  431. type={NewButtonType.link}
  432. fontSize={rpxToPx(24)}
  433. title={moreActive ? '收起' : `展开剩余${hours.length - 3}条`}
  434. btnStyle={{ minWidth: rpxToPx(170) }}
  435. />
  436. </View>
  437. <View className="border_footer_line" />
  438. </View>
  439. }
  440. </View>
  441. }
  442. {
  443. hours.length == 0 && <View className="no_current">
  444. <Text>No Active Hours Yet.</Text>
  445. <View className="border_footer_line" />
  446. </View>
  447. }
  448. </View>
  449. }
  450. function formatTimeInterval(startTimestamp, endTimestamp) {
  451. // 计算时间间隔(以秒为单位)
  452. const intervalSeconds = Math.floor((endTimestamp - startTimestamp) / 1000);
  453. // 处理负值的情况
  454. if (intervalSeconds < 0) {
  455. throw new Error("End timestamp must be greater than start timestamp");
  456. }
  457. // 如果时间间隔大于1小时
  458. if (intervalSeconds >= 3600) {
  459. const hours = Math.floor(intervalSeconds / 3600);
  460. const minutes = Math.floor((intervalSeconds % 3600) / 60);
  461. const seconds = intervalSeconds % 60;
  462. return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  463. } else {
  464. // 如果时间间隔小于1小时
  465. const minutes = Math.floor(intervalSeconds / 60);
  466. const seconds = intervalSeconds % 60;
  467. return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  468. }
  469. }
  470. function consoleFooter() {
  471. if (isExtra()) {
  472. var now = new Date()
  473. var target = new Date()
  474. target.setHours(data.hours[0].hour)
  475. target.setMinutes(50)
  476. target.setSeconds(0)
  477. target.setMilliseconds(0)
  478. var timestamp = target.getTime()
  479. if (now.getTime() > timestamp) {
  480. timestamp += 24 * 3600 * 1000
  481. }
  482. return <Text className="console_footer">{formatTimeInterval(new Date().getTime(), timestamp)}</Text>
  483. }
  484. if (getStatus() == 'open') {
  485. if (new Date().getMinutes() < 10) {
  486. let time = new Date()
  487. time.setMinutes(10)
  488. time.setSeconds(0)
  489. time.setMilliseconds(0)
  490. // 获取总秒数
  491. const totalSeconds = Math.floor((time.getTime() - new Date().getTime()) / 1000);
  492. // 计算小时、分钟和秒
  493. // const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
  494. const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0');
  495. const seconds = String(totalSeconds % 60).padStart(2, '0');
  496. return <Text className="console_footer" style={{ color: getThemeColor('ACTIVE') }}>Ending soon {minutes}:{seconds}</Text>
  497. }
  498. const pre = new Date().getTime()
  499. const next = new Date().getTime() + 3600 * 1000
  500. return <Text className="console_footer">Check In available {dayjs(pre).format('HH:50')}-{dayjs(next).format('HH:10')}</Text>
  501. }
  502. const minute = new Date().getMinutes()
  503. var isChecked = currentHourChecked()
  504. if (isChecked && (minute < 10 || minute >= 50)) {
  505. debugger
  506. let time = new Date()
  507. time.setMinutes(50)
  508. time.setSeconds(0)
  509. time.setMilliseconds(0)
  510. // 获取总秒数
  511. const totalSeconds = Math.floor((time.getTime() + 3600 * 1000 - new Date().getTime()) / 1000);
  512. // 计算小时、分钟和秒
  513. // const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
  514. const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0');
  515. const seconds = String(totalSeconds % 60).padStart(2, '0');
  516. // return `${hours}:${minutes}:${seconds}`;
  517. return <Text className="console_footer">Opening soon {minutes}:{seconds}</Text>
  518. }
  519. let time = new Date()
  520. time.setMinutes(50)
  521. time.setSeconds(0)
  522. time.setMilliseconds(0)
  523. // 获取总秒数
  524. const totalSeconds = Math.floor((time.getTime() - new Date().getTime()) / 1000);
  525. // 计算小时、分钟和秒
  526. // const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
  527. const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0');
  528. const seconds = String(totalSeconds % 60).padStart(2, '0');
  529. return <Text className="console_footer">Opening soon {minutes}:{seconds}</Text>
  530. }
  531. function hourIndex() {
  532. const { time_slots } = data.period
  533. const str = getDuration()
  534. const hour = parseInt(str.substring(0, 2))
  535. var selItem: any = null
  536. time_slots.map((item) => {
  537. if (item.hour == hour) {
  538. selItem = item;
  539. }
  540. })
  541. return <View className="summary_status_bg">
  542. <StatusIndicator
  543. type={StatusType.normal}
  544. color={selItem.type == 'SLEEP' ? getThemeColor('SLEEP') : getThemeColor('ACTIVE')}
  545. text={`${selItem.type == 'SLEEP' ? 'SLEEP HOUR ' : 'WAKING HOUR '}${selItem.index}`}
  546. // fontSize={}
  547. // fontColor={}
  548. />
  549. {/* <View className="summary_status_point" style={{ backgroundColor: selItem.type == 'SLEEP' ? getThemeColor('SLEEP') : getThemeColor('ACTIVE') }} /> */}
  550. {/* <Text className="summary_status_text">{selItem.type == 'SLEEP' ? 'SLEEP HOUR ' : 'WAKING HOUR '}{selItem.index}</Text> */}
  551. </View>
  552. }
  553. function becomeTime() {
  554. var hour = new Date().getHours()
  555. var tHour = parseInt(getDuration().substring(0, 2))
  556. if (isExtra()) {
  557. tHour = data.hours[0].hour
  558. }
  559. if (hour > tHour) {
  560. return `Check in begins at ${getDuration().substring(0, 2)}:50 tomorrow`
  561. }
  562. return `Check in begins at ${getDuration().substring(0, 2)}:50`
  563. }
  564. function consolePanel() {
  565. if (!allowRun) {
  566. return <View className="move_console move_console_auth">
  567. <Text className="console_duration" style={{ color: '#000', fontSize: rpxToPx(34) }}>你还没有开启步数授权</Text>
  568. <Text className="console_open_tip">体验Move More需要您开启微信运动授权,点击下方按钮进行授权</Text>
  569. <View className="console_checkbtn"
  570. onClick={tapLog}
  571. style={{ color: '#fff', backgroundColor: getThemeColor('ACTIVE') }}>去授权</View>
  572. </View>
  573. }
  574. return <View className="move_console">
  575. <View style={{ flex: 1 }} />
  576. {hourIndex()}
  577. <Text className={isExtra() ? 'h24' : 'h34'} style={{
  578. color: MainColorType.g01,
  579. lineHeight: isExtra() ? rpxToPx(28) + 'px' : rpxToPx(42) + 'px'
  580. }}>{getDuration()}</Text>
  581. <View style={{ height: rpxToPx(12) }} />
  582. <Text className={isExtra() ? 'bold h50' : 'bold h34'} style={{
  583. color: isExtra() ? getThemeColor('SLEEP') : getThemeColor('ACTIVE'),
  584. lineHeight: isExtra() ? rpxToPx(60) + 'px' : rpxToPx(42) + 'px'
  585. }}>{
  586. isExtra() ? 'Sleep well.' : getStatus() == 'open' ? `Think you've hit ${getCurrentTarget()} steps this hour?` : `Hit ${getCurrentTarget()} steps within the hour`
  587. }</Text>
  588. <View style={{ height: rpxToPx(48), }} />
  589. {
  590. isExtra() ? <NewButton type={NewButtonType.gray}
  591. width={rpxToPx(502)}
  592. height={rpxToPx(96)}
  593. fontSize={rpxToPx(26)}
  594. bold
  595. // disable
  596. title={becomeTime()}
  597. /> : getStatus() == 'open' ?
  598. <NewButton type={NewButtonType.fill}
  599. onClick={tapLog}
  600. width={rpxToPx(502)}
  601. height={rpxToPx(96)}
  602. fontSize={rpxToPx(26)}
  603. title="I Think So!"
  604. bold
  605. color={getThemeColor('ACTIVE')}
  606. />
  607. :
  608. <NewButton type={NewButtonType.gray}
  609. onClick={tapLog}
  610. width={rpxToPx(502)}
  611. height={rpxToPx(96)}
  612. fontSize={rpxToPx(26)}
  613. bold
  614. // disable
  615. title={becomeTime()}
  616. />
  617. // <View className="console_checkbtn" onClick={tapLog}>{becomeTime()}</View>
  618. }
  619. {
  620. consoleFooter()
  621. }
  622. <View style={{ height: rpxToPx(48) }} />
  623. </View>
  624. }
  625. function toolBtnText() {
  626. return `Upcoming (${data.hours.length - hours.length > 0 ? data.hours.length - hours.length : ''})`
  627. }
  628. function showMore() {
  629. showActionSheet({
  630. showActionSheetWithOptions: showActionSheetWithOptions,
  631. title: t('health.more_actions'),
  632. itemList: [t('health.change_step_goal')],
  633. success: (res) => {
  634. if (res == 0) {
  635. jumpPage('./move_setting_time')
  636. }
  637. }
  638. });
  639. }
  640. function tools() {
  641. return <View className="tools">
  642. <View style={{ width: rpxToPx(74) }} />
  643. <NewButton type={NewButtonType.link}
  644. title={toolBtnText()}
  645. onClick={() => {
  646. jumpPage(`/_health/pages/move_schedule?hours=${JSON.stringify(data.hours)}`)
  647. }}
  648. >
  649. <Image className="calendar" src={require('@assets/_health/calendar.png')} />
  650. </NewButton>
  651. <NewButton type={NewButtonType.more} onClick={() => {
  652. showMore()
  653. }} />
  654. </View>
  655. }
  656. function summary() {
  657. return <View className="move_summary">
  658. <View className="summary_header">
  659. <Text className="h34 bold">Summary</Text>
  660. <View style={{ flex: 1 }} />
  661. {
  662. data.last_updated && <Text style={{ color: '#b2b2b2', fontSize: 10, fontWeight: 'normal' }}>最近更新于 {dayjs(data.last_updated).format('HH:mm')}</Text>
  663. }
  664. <View className="border_footer_line" />
  665. </View>
  666. <View className="summary_content">
  667. <View className="summary_footer">
  668. <View className="summary_footer_item">
  669. <Text className="light_desc">Hours Sedentary</Text>
  670. <Text style={{ fontWeight: 'bold', fontSize: rpxToPx(24) }}><Text style={{ fontSize: rpxToPx(34) }}>{data.stat.sedentary_hours}</Text> / {data.stat.waking_hours}</Text>
  671. </View>
  672. <View className="summary_footer_item">
  673. <Text className="light_desc">Hours Active</Text>
  674. <Text style={{ color: getThemeColor('ACTIVE'), fontWeight: 'bold', fontSize: rpxToPx(24) }}><Text style={{ fontSize: rpxToPx(34) }}>{data.stat.active_hours}</Text> / {data.stat.waking_hours}</Text>
  675. </View>
  676. <View className="summary_footer_item">
  677. <Text className="light_desc">Hours Missed</Text>
  678. <Text style={{ fontWeight: 'bold', fontSize: rpxToPx(24) }}><Text style={{ fontSize: rpxToPx(34) }}>{data.stat.missed_hours}</Text> / {data.stat.waking_hours}</Text>
  679. </View>
  680. </View>
  681. <View className="summary_footer" style={{ marginTop: rpxToPx(48) }}>
  682. <View className="summary_footer_item">
  683. <Text className="light_desc">Total Steps</Text>
  684. <Text style={{ fontWeight: 'bold', fontSize: rpxToPx(24) }}><Text style={{ fontSize: rpxToPx(34) }}>{data.stat.real_steps}</Text> / {data.stat.target_steps}</Text>
  685. </View>
  686. <View className="summary_footer_item">
  687. <Text className="light_desc">Total Calories</Text>
  688. <Text style={{ fontWeight: 'bold', fontSize: rpxToPx(24) }}><Text style={{ fontSize: rpxToPx(34) }}>{data.stat.real_calories}</Text> / {data.stat.target_calories}</Text>
  689. </View>
  690. <View className="summary_footer_item">
  691. <Text className="light_desc">Total Distance</Text>
  692. <Text style={{ fontWeight: 'bold', fontSize: rpxToPx(24) }}><Text style={{ fontSize: rpxToPx(34) }}>{data.stat.real_distance}</Text> / {data.stat.target_distance}</Text>
  693. </View>
  694. </View>
  695. </View>
  696. </View>
  697. }
  698. function goDetail(item) {
  699. jumpPage('./move_detail?id=' + item.id)
  700. }
  701. function historyDate(item, index) {
  702. if (index == 0) {
  703. if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
  704. return '今天'
  705. }
  706. if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
  707. return '昨天'
  708. }
  709. return dayjs(item.timestamp).format('DD')
  710. }
  711. if (dayjs(item.timestamp).format('MM-DD') ==
  712. dayjs(list[index - 1].timestamp).format('MM-DD')) {
  713. return ''
  714. }
  715. if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
  716. return '今天'
  717. }
  718. if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
  719. return '昨天'
  720. }
  721. return dayjs(item.timestamp).format('DD')
  722. }
  723. function historyMonth(item, index) {
  724. if (index == 0) {
  725. if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
  726. return ''
  727. }
  728. if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
  729. return ''
  730. }
  731. return dayjs(item.timestamp).format('MMM')
  732. }
  733. if (dayjs(item.timestamp).format('MM-DD') ==
  734. dayjs(list[index - 1].timestamp).format('MM-DD')) {
  735. return ''
  736. }
  737. if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
  738. return ''
  739. }
  740. if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
  741. return ''
  742. }
  743. return dayjs(item.timestamp).format('MMM')
  744. }
  745. if (!loaded || !loadAuth) return <View />
  746. return <StickyDateList onRefresherRefresh={() => {
  747. setIsPulling(true)
  748. setPage(1)
  749. getMovesHistory(1)
  750. }} isPulling={isPulling}
  751. onScroll={onScroll}
  752. showDate={showDate}
  753. date={date}
  754. loadMore={more}>
  755. <View style={{ display: 'flex', flexDirection: 'column' }}>
  756. <NewHeader type={NewHeaderType.left} title="Move Every Hour" />
  757. {
  758. consolePanel()
  759. }
  760. {
  761. tools()
  762. }
  763. {
  764. summary()
  765. }
  766. {
  767. activeHour()
  768. }
  769. {
  770. list.length > 0 && <View className="history">
  771. <View className="history_header">
  772. <Text className="h42 bold">{t('health.recents')}</Text>
  773. </View>
  774. {
  775. list.map((item, index) => {
  776. return <View key={index} id={`history-${index}`} className="history_item" onClick={() => goDetail(item)}>
  777. {/* <Text className="history_item_date">{(item.date + '').substring(6, 8)}</Text> */}
  778. <View className="cell_date" >
  779. <View className="h42 bold" style={{ lineHeight: rpxToPx(60) + 'px' }}>{historyDate(item,index)}</View>
  780. <View className="h24 bold" style={{ marginLeft: rpxToPx(6), marginTop: rpxToPx(13), lineHeight: rpxToPx(47) + 'px' }}>{historyMonth(item,index)}</View>
  781. </View>
  782. <View className="history_item_detail_bg">
  783. <IconWalk width={rpxToPx(32)} color={MainColorType.active} />
  784. <Text style={{ fontSize: rpxToPx(26), marginLeft: rpxToPx(16) }}> Hours Active {item.active_hours} · Total Steps {item.stat.real_steps}</Text>
  785. </View>
  786. {/* <View className="border_footer_line" /> */}
  787. </View>
  788. })
  789. }
  790. </View>
  791. }
  792. <View style={{backgroundColor:'#fff',height:rpxToPx(40),flexShrink:0}}/>
  793. <ListFooter noMore={(list.length > 0) && (total == list.length)} />
  794. </View>
  795. </StickyDateList>
  796. }