index_time.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. import { View, Text, Image } from "@tarojs/components";
  2. import './index_time.scss';
  3. import { jumpPage } from "@/features/trackTimeDuration/hooks/Common";
  4. import { getCommon, getDot } from "@/features/trackTimeDuration/hooks/RingData";
  5. import Rings, { RingCommon, BgRing, TargetRing, CurrentDot } from "@/features/trackTimeDuration/components/Rings";
  6. import { useEffect, useState } from "react";
  7. import DeviceInfo from 'react-native-device-info';
  8. import dayjs from "dayjs";
  9. import moment from 'moment-timezone'
  10. import Modal from "@/components/layout/Modal.weapp";
  11. import { getResources, loginAnonymous, place, systemVersion } from "./services/net";
  12. import Taro, { useDidShow } from "@tarojs/taro";
  13. import { useTranslation } from "react-i18next";
  14. import 'dayjs/locale/zh-cn';
  15. import 'dayjs/locale/en';
  16. import momentT from 'moment';
  17. import { getTimezone, getTimezoneName, kIsAndroid } from "@/utils/tools";
  18. import { AppState, Linking, SafeAreaView } from "react-native";
  19. import { method } from "lodash";
  20. import { AtActivityIndicator } from "taro-ui";
  21. import showAlert from "@/components/basic/Alert";
  22. // import 'moment/locale/en';
  23. let showUpdate = false;
  24. let LinearGradient, useActionSheet, useNavigation
  25. if (process.env.TARO_ENV == 'rn') {
  26. LinearGradient = require('react-native-linear-gradient').default
  27. useActionSheet = require('@expo/react-native-action-sheet').useActionSheet
  28. useNavigation = require("@react-navigation/native").useNavigation
  29. }
  30. export default function IndexTimePage() {
  31. let navigation;
  32. if (useNavigation) {
  33. navigation = useNavigation()
  34. }
  35. const [count, setCount] = useState(0)
  36. const [showLanguage, setShowLanguage] = useState(false)
  37. const [isEn, setIsEn] = useState(true)
  38. const [showDst, setShowDst] = useState(false)
  39. const [current, setCurrent] = useState<any>(null)
  40. const [loading, setLoading] = useState(false)
  41. const [resources, setResources] = useState<any>([])
  42. const { t, i18n } = useTranslation()
  43. useEffect(() => {
  44. getLanguage();
  45. getResources().then(res => {
  46. setResources(res)
  47. })
  48. AppState.addEventListener('change', handleAppStateChange);
  49. login()
  50. setInterval(() => {
  51. setCount(t => t + 1)
  52. const now = new Date()
  53. const seconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds()
  54. if (current && current.time && current.time.timezone.use_dst) {
  55. if (current.time_changes && current.time_changes.length > 1) {
  56. const date = new Date(current.time_changes[1].timestamp)
  57. const seconds2 = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds()
  58. if (seconds == seconds2) {
  59. refresh()
  60. }
  61. }
  62. }
  63. }, 1000)
  64. }, [])
  65. function login() {
  66. loginAnonymous(kIsAndroid ? DeviceInfo.getAndroidIdSync() : DeviceInfo.getDeviceId()).then(res => {
  67. global.token = (res as any).token
  68. Taro.setStorage({
  69. key: 'token',
  70. data: (res as any).token
  71. })
  72. checkVersionUpdate()
  73. }).catch(e => {
  74. console.log('login error', e)
  75. })
  76. }
  77. useDidShow(() => {
  78. checkVersionUpdate()
  79. })
  80. const handleAppStateChange = (nextAppState) => {
  81. console.log(nextAppState)
  82. if (nextAppState != 'active') {
  83. return
  84. }
  85. if (nextAppState == 'active') {
  86. console.log('active')
  87. refresh()
  88. }
  89. };
  90. async function checkVersionUpdate() {
  91. systemVersion('time').then(res => {
  92. if ((res as any).result == 'UPDATE' && !showUpdate) {
  93. showUpdate = true
  94. showAlert({
  95. title: (res as any).title,
  96. content: (res as any).description,
  97. showCancel: true,
  98. confirmText: 'Update',
  99. confirm: () => {
  100. Linking.openURL((res as any).download_url)
  101. }
  102. })
  103. }
  104. else if ((res as any).result == 'FORCE_UPDATE') {
  105. showAlert({
  106. title: (res as any).title,
  107. content: (res as any).description,
  108. showCancel: false,
  109. confirmText: 'Update',
  110. confirm: () => {
  111. Linking.openURL((res as any).download_url)
  112. }
  113. })
  114. }
  115. })
  116. }
  117. function refresh() {
  118. if (current) {
  119. chooseLocation({
  120. lat: current.geo.lat,
  121. lng: current.geo.lng
  122. }, 'QUERY')
  123. }
  124. }
  125. async function getLanguage() {
  126. var strLocation = await getStorage('lastLocation')
  127. if (strLocation) {
  128. setCurrent(JSON.parse(strLocation))
  129. }
  130. var language = await getStorage('language') || 'en'
  131. global.language = language;
  132. i18n.changeLanguage(language)
  133. setIsEn(language == 'en')
  134. dayjs.locale(language == 'en' ? 'en' : 'zh-cn');
  135. require('moment/locale/en-gb')
  136. require('moment/locale/zh-cn')
  137. momentT.locale(language == 'en' ? 'en-gb' : 'zh-cn')
  138. moment.defineLocale(language == 'en' ? 'en-gb' : 'zh-cn', (momentT.localeData() as any)._config)
  139. moment.locale(language == 'en' ? 'en-gb' : 'zh-cn')
  140. var token = await getStorage('token') || ''
  141. global.token = token
  142. refresh()
  143. // moment.locale(language == 'en' ? 'en' : 'zh-cn');
  144. }
  145. async function getStorage(key: string) {
  146. try {
  147. const res = await Taro.getStorage({ key });
  148. return res.data;
  149. } catch {
  150. return '';
  151. }
  152. }
  153. function changeLanguage(isEnglish: boolean) {
  154. global.language = isEnglish ? 'en' : 'zh';
  155. i18n.changeLanguage(isEnglish ? 'en' : 'zh')
  156. setIsEn(isEnglish)
  157. Taro.setStorage({ key: 'language', data: isEnglish ? 'en' : 'zh' })
  158. setShowLanguage(false)
  159. dayjs.locale(isEnglish ? 'en' : 'zh-cn');
  160. require('moment/locale/en-gb')
  161. require('moment/locale/zh-cn')
  162. momentT.locale(isEnglish ? 'en-gb' : 'zh-cn')
  163. moment.defineLocale(isEnglish ? 'en-gb' : 'zh-cn', (momentT.localeData() as any)._config)
  164. moment.locale(isEnglish ? 'en-gb' : 'zh-cn')
  165. // moment.locale(isEnglish ? 'en' : 'zh-cn');
  166. if (current) {
  167. chooseLocation({
  168. lat: current.geo.lat,
  169. lng: current.geo.lng
  170. }, 'QUERY')
  171. }
  172. }
  173. async function goMap() {
  174. if (loading) {
  175. return;
  176. }
  177. const zoom = await getStorage('zoom') || 8
  178. jumpPage('', 'map', navigation, {
  179. source: 'time_of_day',
  180. zoom: zoom,
  181. lat: current ? current.geo.lat : null,
  182. lng: current ? current.geo.lng : null,
  183. chooseLocation: chooseLocation
  184. })
  185. }
  186. function goRecords() {
  187. if (loading) {
  188. return;
  189. }
  190. if (!current) {
  191. return
  192. }
  193. jumpPage('', 'MyPlaces', navigation, {
  194. clearLocation: clearLocation,
  195. changeLocation: changeLocation
  196. })
  197. }
  198. function showPrivacy() {
  199. const resource = resources.filter((item: any) => {
  200. const code = isEn ? "time_privacy_en" : "time_privacy_cn"
  201. return item.code == code
  202. })
  203. if (!resource || resource.length == 0) {
  204. Taro.showToast({
  205. title: 'Not found.',
  206. icon: 'none'
  207. })
  208. return
  209. }
  210. jumpPage('/pages/common/H5?title=User Agreement&url=' + resource[0].url, 'H5', navigation, {
  211. title: resource[0].name,
  212. url: resource[0].url
  213. })
  214. }
  215. function chooseLocation(res: any, method: string) {
  216. if (res.lng < -180) {
  217. res.lng += 360
  218. }
  219. if (res.lng > 180) {
  220. res.lng -= 360
  221. }
  222. if (res.lat > 90) {
  223. res.lat -= 90
  224. }
  225. if (res.lat < -90) {
  226. res.lat += 90
  227. }
  228. setLoading(true)
  229. place(res.lat, res.lng, method).then(res => {
  230. console.log(res)
  231. setCurrent((res as any).location)
  232. saveLocation((res as any).location)
  233. setLoading(false)
  234. }).catch(e => {
  235. console.log(e)
  236. setLoading(false)
  237. })
  238. }
  239. async function saveLocation(location: any) {
  240. Taro.setStorage({ key: 'lastLocation', data: JSON.stringify(location) })
  241. var locations = await getStorage('locations')
  242. var list: any = []
  243. var isFind = false;
  244. if (locations) {
  245. list = JSON.parse(locations)
  246. }
  247. list.map(item => {
  248. if (item.geo.lat == location.geo.lat && item.geo.lng == location.geo.lng) {
  249. isFind = true
  250. }
  251. })
  252. if (!isFind) {
  253. list.push(location)
  254. }
  255. Taro.setStorage({ key: 'locations', data: JSON.stringify(list) })
  256. }
  257. function clearLocation() {
  258. Taro.clearStorage()
  259. setCurrent(null)
  260. }
  261. function changeLocation(location) {
  262. setCurrent(location)
  263. Taro.setStorage({ key: 'lastLocation', data: JSON.stringify(location) })
  264. chooseLocation({
  265. lat: location.geo.lat,
  266. lng: location.geo.lng
  267. }, 'SWITCH')
  268. }
  269. function getLocation() {
  270. if (current.geo) {
  271. if (current.geo.city) {
  272. return current.geo.city
  273. }
  274. if (current.geo.country) {
  275. return current.geo.country
  276. }
  277. return `${Math.abs(parseInt(current.geo.lat))}°${parseInt(current.geo.lat) < 0 ? 'S' : 'N'} ${Math.abs(parseInt(current.geo.lng))}°${parseInt(current.geo.lng) < 0 ? 'W' : 'E'}`
  278. }
  279. return t('time_of_day.index.unknown')
  280. }
  281. function getTimezone1() {
  282. if (current.time && current.time.timezone) {
  283. return current.time.timezone.offset_id + ' · ' + current.time.timezone.name
  284. }
  285. return ''
  286. }
  287. const common: RingCommon = {
  288. useCase: 'ChooseScenario',
  289. radius: 115,
  290. lineWidth: 26,
  291. isFast: true,
  292. status: 'WAIT_FOR_START'
  293. }
  294. const bgRing: BgRing = {
  295. color: '#EAE9E9'
  296. }
  297. function getArc() {
  298. var hour = new Date().getHours()
  299. var minute = new Date().getMinutes()
  300. var second = new Date().getSeconds()
  301. if (current) {
  302. var strHour = moment().tz(current.time.timezone.id).format('H')
  303. hour = parseInt(strHour)
  304. var strMin = moment().tz(current.time.timezone.id).format('m')
  305. minute = parseInt(strMin)
  306. var strSecond = moment().tz(current.time.timezone.id).format('s')
  307. second = parseInt(strSecond)
  308. }
  309. return (hour * 3600 + minute * 60 + second) / (24 * 3600) * 2 * Math.PI
  310. }
  311. function ring() {
  312. var offset = 0
  313. var hour = new Date().getHours()
  314. var minute = new Date().getMinutes()
  315. if (current) {
  316. var strHour = moment().tz(current.time.timezone.id).format('H')
  317. hour = parseInt(strHour)
  318. var strMin = moment().tz(current.time.timezone.id).format('m')
  319. minute = parseInt(strMin)
  320. }
  321. if (hour != new Date().getHours() || minute != new Date().getMinutes()) {
  322. offset = hour * 60 + minute - new Date().getHours() * 60 - new Date().getMinutes()
  323. }
  324. const currentDot: CurrentDot = {
  325. color: '#CACACA',
  326. lineWidth: 10,
  327. borderColor: '#fff',
  328. offset: offset,
  329. whiteIcon: true
  330. }
  331. const targetRing: TargetRing = {
  332. color: '#CACACA',
  333. startArc: -Math.PI / 2,
  334. durationArc: getArc()
  335. }
  336. return <Rings common={common} bgRing={bgRing} targetRing={targetRing} currentDot={currentDot} canvasId={'smal11l'} />
  337. }
  338. function languageModalContent() {
  339. return <View className="modal_bg">
  340. <View className="language_content">
  341. <View className="language_item" onClick={() => changeLanguage(isEn ? true : false)}>
  342. <Text className="language_text">{isEn ? 'English' : '中文'}</Text>
  343. <Image className="check" src={require('@assets/index_time/check.png')} />
  344. </View>
  345. <View className="line" />
  346. <View className="language_item" onClick={() => changeLanguage(isEn ? false : true)}>
  347. <Text className="language_text">{!isEn ? 'English' : '中文'}</Text>
  348. </View>
  349. </View>
  350. </View>
  351. }
  352. function formatTime(format: string, timestamp?: number) {
  353. // var moment = require('moment-timezone');
  354. if (current) {
  355. if (timestamp) {
  356. return moment(timestamp).tz(current.time.timezone.id).format(format)
  357. }
  358. return moment().tz(current.time.timezone.id).format(format)
  359. }
  360. return dayjs().format(format)
  361. }
  362. function dstTitle(index: number) {
  363. if (index == 0) {
  364. return t('time_of_day.index.last_change')
  365. }
  366. return t('time_of_day.index.next_change')
  367. }
  368. function dstDesc(item: any, index: number) {
  369. if (index == 0) {
  370. if (item.new_dst > 0) {
  371. return t('time_of_day.index.summer_started')
  372. }
  373. return t('time_of_day.index.winter_started')
  374. }
  375. if (item.new_dst > 0) {
  376. return t('time_of_day.index.summer_starts')
  377. }
  378. return t('time_of_day.index.winter_starts')
  379. }
  380. function isNight() {
  381. var hour = new Date().getHours()
  382. if (current) {
  383. var strHour = moment().tz(current.time.timezone.id).format('H')
  384. hour = parseInt(strHour)
  385. }
  386. if (hour >= 18 || hour < 6) {
  387. return true
  388. }
  389. return false
  390. }
  391. function dstModalContent() {
  392. return <View className="modal_bg">
  393. {
  394. current.time_changes.map((item, index) => {
  395. return <View className="dst_content" key={index}>
  396. <View className="dst_item">
  397. <View className="dst_card">
  398. <Text className="month">{formatTime('MMMM', item.timestamp)}</Text>
  399. <Text className="day">{formatTime('D', item.timestamp)}</Text>
  400. <Text className="week">{formatTime('dddd', item.timestamp)}</Text>
  401. <Text className="year">{formatTime('YYYY', item.timestamp)}</Text>
  402. </View>
  403. </View>
  404. <View style={{ flex: 1 }}>
  405. <Text className="dst_title">{item.title}</Text>
  406. <Text className="dst_desc" style={{ marginBottom: 10 }}>{item.subtitle}</Text>
  407. {
  408. item.descriptions.map((desc, j) => {
  409. return <Text className="dst_note" key={j * 100}>· {desc}</Text>
  410. })
  411. }
  412. </View>
  413. </View>
  414. })
  415. }
  416. </View>
  417. }
  418. function detail() {
  419. return <View className="page_container">
  420. <View style={{ position: 'relative' }}>
  421. {
  422. ring()
  423. }
  424. <View className="ring_center">
  425. <Image className="sun" src={isNight() ? require('@assets/index_time/moon.png') : require('@assets/index_time/sun.png')} />
  426. <Text className="time">{formatTime('HH:mm:ss')}</Text>
  427. <Text className="date1">{global.language == 'en' ? formatTime('dddd, MMM D') : formatTime('MMMD日 dddd')}</Text>
  428. </View>
  429. </View>
  430. <View className="location">
  431. {
  432. loading && <View style={{ display: 'flex', overflow: 'hidden', height: 20, marginRight: 5 }}><AtActivityIndicator color="#CACACA" /></View>
  433. }
  434. {
  435. loading && <Text className="location_text">{t('time_of_day.index.updating')}</Text>
  436. }
  437. {
  438. !loading && <Image className="location_icon" src={require('@assets/index_time/pin.png')} />
  439. }
  440. {
  441. !loading && <Text className="location_text">{current ? getLocation() : t('time_of_day.index.unknown')}</Text>
  442. }
  443. </View>
  444. <View style={{ flexDirection: 'row', marginTop: 10, alignItems: 'center', opacity: loading ? 0 : 1 }} onClick={() => {
  445. if (current && current.time && current.time.timezone.use_dst) {
  446. setShowDst(true)
  447. }
  448. }}>
  449. <Text className="timezone">{current ? getTimezone1() : getTimezone()}</Text>
  450. {
  451. current && current.time && current.time.timezone.use_dst && <Image className="location_icon" src={require('@assets/index_time/arrow_right.png')} />
  452. }
  453. </View>
  454. <View className="btn_bg2" style={{ opacity: loading ? 0.4 : 1 }} onClick={goMap} onLongClick={goRecords} onLongPress={goRecords} onLongTap={goRecords}>
  455. <Image className="btn_img_bg" src={require('@assets/index_time/btn_bg.png')} />
  456. <View className="btn_text_bg">
  457. <Text className="btn_text">{!current ? t('time_of_day.index.pick_location') : t('time_of_day.index.change_location')}</Text>
  458. </View>
  459. {
  460. current && <Image className="more" onClick={goRecords} src={require('@assets/index_time/more.png')} />
  461. }
  462. </View>
  463. {/* <View className="btn_bg" onClick={goMap} onLongClick={goRecords} onLongPress={goRecords} onLongTap={goRecords}>
  464. <Text className="btn_text">{!current ? t('time_of_day.index.pick_location') : t('time_of_day.index.change_location')}</Text>
  465. {
  466. current && <Image className="more" onClick={goRecords} src={require('@assets/index_time/more.png')} />
  467. }
  468. </View> */}
  469. <View className="footer" >
  470. <Text className="footer_text">{t('time_of_day.index.version')}</Text>
  471. <Text className="footer_text" onClick={showPrivacy} style={{ paddingLeft: 20, paddingRight: 20 }}>{t('time_of_day.index.privacy')}</Text>
  472. <Text className="footer_text" onClick={() => setShowLanguage(true)}>{isEn ? 'English' : '中文'}</Text>
  473. <Image className="footer_icon" src={require('@assets/index_time/arrow.png')} onClick={() => setShowLanguage(true)} />
  474. </View>
  475. {
  476. showLanguage && <Modal
  477. testInfo={null}
  478. themeIsWhite={true}
  479. dismiss={() => {
  480. setShowLanguage(false)
  481. }}
  482. confirm={() => { }}>
  483. {
  484. languageModalContent()
  485. }
  486. </Modal>
  487. }
  488. {
  489. showDst && <Modal
  490. testInfo={null}
  491. themeIsWhite={true}
  492. dismiss={() => {
  493. setShowDst(false)
  494. }}
  495. confirm={() => { }}>
  496. {
  497. dstModalContent()
  498. }
  499. </Modal>
  500. }
  501. </View>
  502. }
  503. return detail()
  504. }