move.tsx 32 KB

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