From c73a82c16a7dd97c111876d6a2e729cf02e69277 Mon Sep 17 00:00:00 2001 From: fxy060608 Date: Tue, 23 Jul 2019 13:45:26 +0800 Subject: [PATCH] sync api --- lib/h5/main.js | 2 +- package.json | 4 +- packages/uni-app-plus-nvue/dist/uni.js | 28 +- .../services/api/context/live-pusher.js | 89 ++-- .../app-plus-nvue/services/api/context/map.js | 27 +- .../services/api/context/video.js | 121 ++--- .../app-plus-nvue/services/api/util.js | 37 +- .../app-plus/service/api/constants.js | 7 + .../app-plus/service/api/context/audio.js | 148 ++++++ .../service/api/context/background-audio.js | 181 +++++++ .../app-plus/service/api/context/map.js | 90 ++++ .../app-plus/service/api/context/video.js | 39 ++ .../service/api/device/accelerometer.js | 55 ++ .../service/api/device/add-phone-contact.js | 230 ++++++++ .../app-plus/service/api/device/bluetooth.js | 141 +++++ .../app-plus/service/api/device/brightness.js | 24 + .../app-plus/service/api/device/clipboard.js | 30 ++ .../app-plus/service/api/device/compass.js | 55 ++ .../service/api/device/get-network-type.js | 10 + .../app-plus/service/api/device/ibeacon.js | 79 +++ .../service/api/device/make-phone-call.js | 8 + .../app-plus/service/api/device/scan-code.js | 184 +++++++ .../app-plus/service/api/device/system.js | 57 ++ .../app-plus/service/api/device/vibrate.js | 12 + .../app-plus/service/api/file/file.js | 181 +++++++ .../service/api/file/open-document.js | 23 + src/platforms/app-plus/service/api/index.js | 57 ++ .../service/api/location/choose-location.js | 82 +++ .../service/api/location/get-location.js | 75 +++ .../service/api/location/open-location.js | 52 ++ .../app-plus/service/api/media/audio.js | 107 ++++ .../service/api/media/choose-image.js | 154 ++++++ .../service/api/media/choose-video.js | 97 ++++ .../service/api/media/compress-image.js | 28 + .../service/api/media/get-image-info.js | 23 + .../service/api/media/preview-image.js | 84 +++ .../app-plus/service/api/media/recorder.js | 77 +++ .../api/media/save-image-to-photos-album.js | 21 + .../api/media/save-video-to-photos-album.js | 21 + .../service/api/network/download-file.js | 90 ++++ .../app-plus/service/api/network/request.js | 138 +++++ .../app-plus/service/api/network/socket.js | 93 ++++ .../service/api/network/upload-file.js | 112 ++++ .../service/api/plugin/get-provider.js | 76 +++ .../app-plus/service/api/plugin/oauth.js | 123 +++++ .../app-plus/service/api/plugin/payment.js | 31 ++ .../app-plus/service/api/plugin/push.js | 65 +++ .../app-plus/service/api/plugin/share.js | 197 +++++++ .../app-plus/service/api/ui/keyboard.js | 13 + .../app-plus/service/api/ui/navigation-bar.js | 70 +++ .../app-plus/service/api/ui/popup.js | 170 ++++++ .../service/api/ui/pull-down-refresh.js | 38 ++ .../app-plus/service/api/ui/tab-bar.js | 83 +++ src/platforms/app-plus/service/api/util.js | 167 ++++++ .../service/framework/plus-message.js | 36 ++ .../app-plus/service/framework/safe-area.js | 9 + .../app-plus/service/framework/tabbar.js | 497 ++++++++++++++++++ 57 files changed, 4601 insertions(+), 147 deletions(-) create mode 100644 src/platforms/app-plus/service/api/constants.js create mode 100644 src/platforms/app-plus/service/api/context/audio.js create mode 100644 src/platforms/app-plus/service/api/context/background-audio.js create mode 100644 src/platforms/app-plus/service/api/context/map.js create mode 100644 src/platforms/app-plus/service/api/context/video.js create mode 100644 src/platforms/app-plus/service/api/device/accelerometer.js create mode 100644 src/platforms/app-plus/service/api/device/add-phone-contact.js create mode 100644 src/platforms/app-plus/service/api/device/bluetooth.js create mode 100644 src/platforms/app-plus/service/api/device/brightness.js create mode 100644 src/platforms/app-plus/service/api/device/clipboard.js create mode 100644 src/platforms/app-plus/service/api/device/compass.js create mode 100644 src/platforms/app-plus/service/api/device/get-network-type.js create mode 100644 src/platforms/app-plus/service/api/device/ibeacon.js create mode 100644 src/platforms/app-plus/service/api/device/make-phone-call.js create mode 100644 src/platforms/app-plus/service/api/device/scan-code.js create mode 100644 src/platforms/app-plus/service/api/device/system.js create mode 100644 src/platforms/app-plus/service/api/device/vibrate.js create mode 100644 src/platforms/app-plus/service/api/file/file.js create mode 100644 src/platforms/app-plus/service/api/file/open-document.js create mode 100644 src/platforms/app-plus/service/api/index.js create mode 100644 src/platforms/app-plus/service/api/location/choose-location.js create mode 100644 src/platforms/app-plus/service/api/location/get-location.js create mode 100644 src/platforms/app-plus/service/api/location/open-location.js create mode 100644 src/platforms/app-plus/service/api/media/audio.js create mode 100644 src/platforms/app-plus/service/api/media/choose-image.js create mode 100644 src/platforms/app-plus/service/api/media/choose-video.js create mode 100644 src/platforms/app-plus/service/api/media/compress-image.js create mode 100644 src/platforms/app-plus/service/api/media/get-image-info.js create mode 100644 src/platforms/app-plus/service/api/media/preview-image.js create mode 100644 src/platforms/app-plus/service/api/media/recorder.js create mode 100644 src/platforms/app-plus/service/api/media/save-image-to-photos-album.js create mode 100644 src/platforms/app-plus/service/api/media/save-video-to-photos-album.js create mode 100644 src/platforms/app-plus/service/api/network/download-file.js create mode 100644 src/platforms/app-plus/service/api/network/request.js create mode 100644 src/platforms/app-plus/service/api/network/socket.js create mode 100644 src/platforms/app-plus/service/api/network/upload-file.js create mode 100644 src/platforms/app-plus/service/api/plugin/get-provider.js create mode 100644 src/platforms/app-plus/service/api/plugin/oauth.js create mode 100644 src/platforms/app-plus/service/api/plugin/payment.js create mode 100644 src/platforms/app-plus/service/api/plugin/push.js create mode 100644 src/platforms/app-plus/service/api/plugin/share.js create mode 100644 src/platforms/app-plus/service/api/ui/keyboard.js create mode 100644 src/platforms/app-plus/service/api/ui/navigation-bar.js create mode 100644 src/platforms/app-plus/service/api/ui/popup.js create mode 100644 src/platforms/app-plus/service/api/ui/pull-down-refresh.js create mode 100644 src/platforms/app-plus/service/api/ui/tab-bar.js create mode 100644 src/platforms/app-plus/service/api/util.js create mode 100644 src/platforms/app-plus/service/framework/plus-message.js create mode 100644 src/platforms/app-plus/service/framework/safe-area.js create mode 100644 src/platforms/app-plus/service/framework/tabbar.js diff --git a/lib/h5/main.js b/lib/h5/main.js index 18ff3bca3..89656dca6 100644 --- a/lib/h5/main.js +++ b/lib/h5/main.js @@ -36,4 +36,4 @@ Vue.use(require('uni-view/plugins').default, { require('uni-core/vue') require('uni-platform/components') -require('uni-components') +// require('uni-components') diff --git a/package.json b/package.json index 044c98ad9..e450eeb5c 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,9 @@ "Vue": true, "wx": true, "my": true, - "swan": true, + "swan": true, + "weex": true, + "__id__": true, "__uniConfig": true, "__uniRoutes": true, "__registerPage": true, diff --git a/packages/uni-app-plus-nvue/dist/uni.js b/packages/uni-app-plus-nvue/dist/uni.js index 35c53bec9..739900e27 100644 --- a/packages/uni-app-plus-nvue/dist/uni.js +++ b/packages/uni-app-plus-nvue/dist/uni.js @@ -2433,22 +2433,24 @@ function getLastWebview () { } } -function isTabBarPage () { +function isTabBarPage (route = '') { if (!(__uniConfig.tabBar && Array.isArray(__uniConfig.tabBar.list))) { return false } try { - const pages = getCurrentPages(); - if (!pages.length) { - return false - } - const page = pages[pages.length - 1]; - if (!page) { - return false - } - const route = page.route; + if (!route) { + const pages = getCurrentPages(); + if (!pages.length) { + return false + } + const page = pages[pages.length - 1]; + if (!page) { + return false + } + route = page.route; + } return !!__uniConfig.tabBar.list.find(tabBarPage => { - const pagePath = tabBarPage.path; + const pagePath = tabBarPage.pagePath; return pagePath === route || pagePath === (route + '.html') }) } catch (e) { @@ -2586,11 +2588,11 @@ const outOfChina = function (lng, lat) { return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false) }; -function invoke(...args) { +function invoke (...args) { return UniServiceJSBridge.invoke(...args) } -function publish(...args) { +function publish (...args) { return UniServiceJSBridge.publish(...args) } diff --git a/src/platforms/app-plus-nvue/services/api/context/live-pusher.js b/src/platforms/app-plus-nvue/services/api/context/live-pusher.js index 506fe1d8b..9529d1ef4 100644 --- a/src/platforms/app-plus-nvue/services/api/context/live-pusher.js +++ b/src/platforms/app-plus-nvue/services/api/context/live-pusher.js @@ -1,76 +1,79 @@ import { - findRefById, + findElmById, invokeVmMethod, invokeVmMethodWithoutArgs } from '../util' class LivePusherContext { - constructor (id, ctx) { + constructor(id, ctx) { this.id = id this.ctx = ctx } - start (cbs) { + start(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'start', cbs) } - stop (cbs) { + stop(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'stop', cbs) } - pause (cbs) { + pause(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'pause', cbs) - } - - resume (cbs) { - return invokeVmMethodWithoutArgs(this.ctx, 'resume', cbs) } - switchCamera (cbs) { + resume(cbs) { + return invokeVmMethodWithoutArgs(this.ctx, 'resume', cbs) + } + + switchCamera(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'switchCamera', cbs) } - snapshot (cbs) { + snapshot(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'snapshot', cbs) } - toggleTorch (cbs) { + toggleTorch(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'toggleTorch', cbs) } - - playBGM (args) { - return invokeVmMethod(this.ctx, 'playBGM', args) - } - - stopBGM (cbs) { - return invokeVmMethodWithoutArgs(this.ctx, 'stopBGM', cbs) - } - - pauseBGM (cbs) { - return invokeVmMethodWithoutArgs(this.ctx, 'pauseBGM', cbs) - } - - resumeBGM (cbs) { - return invokeVmMethodWithoutArgs(this.ctx, 'resumeBGM', cbs) - } - - setBGMVolume (cbs) { - return invokeVmMethod(this.ctx, 'setBGMVolume', cbs) - } - - startPreview (cbs) { - return invokeVmMethodWithoutArgs(this.ctx, 'startPreview', cbs) - } - - stopPreview (args) { + + playBGM(args) { + return invokeVmMethod(this.ctx, 'playBGM', args) + } + + stopBGM(cbs) { + return invokeVmMethodWithoutArgs(this.ctx, 'stopBGM', cbs) + } + + pauseBGM(cbs) { + return invokeVmMethodWithoutArgs(this.ctx, 'pauseBGM', cbs) + } + + resumeBGM(cbs) { + return invokeVmMethodWithoutArgs(this.ctx, 'resumeBGM', cbs) + } + + setBGMVolume(cbs) { + return invokeVmMethod(this.ctx, 'setBGMVolume', cbs) + } + + startPreview(cbs) { + return invokeVmMethodWithoutArgs(this.ctx, 'startPreview', cbs) + } + + stopPreview(args) { return invokeVmMethodWithoutArgs(this.ctx, 'stopPreview', args) - } + } } -export function createLivePusherContext (id, vm) { - const ref = findRefById(id, vm) - if (!ref) { +export function createLivePusherContext(id, vm) { + if (!vm) { + global.nativeLog('uni.createLivePusherContext 必须传入第二个参数,即当前 vm 对象(this)', '__WARN') + } + const elm = findElmById(id, vm) + if (!elm) { global.nativeLog('Can not find `' + id + '`', '__WARN') } - return new LivePusherContext(id, vm.$refs[ref]) + return new LivePusherContext(id, elm) } diff --git a/src/platforms/app-plus-nvue/services/api/context/map.js b/src/platforms/app-plus-nvue/services/api/context/map.js index 233911474..51c2e4e99 100644 --- a/src/platforms/app-plus-nvue/services/api/context/map.js +++ b/src/platforms/app-plus-nvue/services/api/context/map.js @@ -1,44 +1,47 @@ import { - findRefById, + findElmById, invokeVmMethod, invokeVmMethodWithoutArgs } from '../util' class MapContext { - constructor (id, ctx) { + constructor(id, ctx) { this.id = id this.ctx = ctx } - getCenterLocation (cbs) { + getCenterLocation(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'getCenterLocation', cbs) } - moveToLocation () { + moveToLocation() { return invokeVmMethodWithoutArgs(this.ctx, 'moveToLocation') } - translateMarker (args) { + translateMarker(args) { return invokeVmMethod(this.ctx, 'translateMarker', args, ['animationEnd']) } - includePoints (args) { + includePoints(args) { return invokeVmMethod(this.ctx, 'includePoints', args) } - getRegion (cbs) { + getRegion(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'getRegion', cbs) } - getScale (cbs) { + getScale(cbs) { return invokeVmMethodWithoutArgs(this.ctx, 'getScale', cbs) } } -export function createMapContext (id, vm) { - const ref = findRefById(id, vm) - if (!ref) { +export function createMapContext(id, vm) { + if (!vm) { + global.nativeLog('uni.createMapContext 必须传入第二个参数,即当前 vm 对象(this)', '__WARN') + } + const elm = findElmById(id, vm) + if (!elm) { global.nativeLog('Can not find `' + id + '`', '__WARN') } - return new MapContext(id, vm.$refs[ref]) + return new MapContext(id, elm) } diff --git a/src/platforms/app-plus-nvue/services/api/context/video.js b/src/platforms/app-plus-nvue/services/api/context/video.js index 0659a83d5..181608ac4 100644 --- a/src/platforms/app-plus-nvue/services/api/context/video.js +++ b/src/platforms/app-plus-nvue/services/api/context/video.js @@ -1,60 +1,63 @@ -import { - findRefById, - invokeVmMethod, - invokeVmMethodWithoutArgs -} from '../util' - -class VideoContext { - constructor (id, ctx) { - this.id = id - this.ctx = ctx - } - - play () { - return invokeVmMethodWithoutArgs(this.ctx, 'play') - } - - pause () { - return invokeVmMethodWithoutArgs(this.ctx, 'pause') - } - - seek (args) { - return invokeVmMethod(this.ctx, 'seek', args) - } - - stop () { - return invokeVmMethodWithoutArgs(this.ctx, 'stop') - } - - sendDanmu (args) { - return invokeVmMethod(this.ctx, 'sendDanmu', args) - } - - playbackRate (args) { - return invokeVmMethod(this.ctx, 'playbackRate', args) - } - - requestFullScreen (args) { - return invokeVmMethod(this.ctx, 'requestFullScreen', args) - } - - exitFullScreen () { - return invokeVmMethodWithoutArgs(this.ctx, 'exitFullScreen') - } - - showStatusBar () { - return invokeVmMethodWithoutArgs(this.ctx, 'showStatusBar') - } - - hideStatusBar () { - return invokeVmMethodWithoutArgs(this.ctx, 'hideStatusBar') - } -} - -export function createVideoContext (id, vm) { - const ref = findRefById(id, vm) - if (!ref) { - global.nativeLog('Can not find `' + id + '`', '__WARN') - } - return new VideoContext(id, vm.$refs[ref]) +import { + findElmById, + invokeVmMethod, + invokeVmMethodWithoutArgs +} from '../util' + +class VideoContext { + constructor(id, ctx) { + this.id = id + this.ctx = ctx + } + + play() { + return invokeVmMethodWithoutArgs(this.ctx, 'play') + } + + pause() { + return invokeVmMethodWithoutArgs(this.ctx, 'pause') + } + + seek(args) { + return invokeVmMethod(this.ctx, 'seek', args) + } + + stop() { + return invokeVmMethodWithoutArgs(this.ctx, 'stop') + } + + sendDanmu(args) { + return invokeVmMethod(this.ctx, 'sendDanmu', args) + } + + playbackRate(args) { + return invokeVmMethod(this.ctx, 'playbackRate', args) + } + + requestFullScreen(args) { + return invokeVmMethod(this.ctx, 'requestFullScreen', args) + } + + exitFullScreen() { + return invokeVmMethodWithoutArgs(this.ctx, 'exitFullScreen') + } + + showStatusBar() { + return invokeVmMethodWithoutArgs(this.ctx, 'showStatusBar') + } + + hideStatusBar() { + return invokeVmMethodWithoutArgs(this.ctx, 'hideStatusBar') + } +} + +export function createVideoContext(id, vm) { + if (!vm) { + global.nativeLog('uni.createVideoContext 必须传入第二个参数,即当前 vm 对象(this)', '__WARN') + } + const elm = findElmById(id, vm) + if (!elm) { + global.nativeLog('Can not find `' + id + '`', '__WARN') + } + return new VideoContext(id, elm) } diff --git a/src/platforms/app-plus-nvue/services/api/util.js b/src/platforms/app-plus-nvue/services/api/util.js index 62de2a7a2..ba19653aa 100644 --- a/src/platforms/app-plus-nvue/services/api/util.js +++ b/src/platforms/app-plus-nvue/services/api/util.js @@ -9,7 +9,7 @@ const CALLBACKS = [SUCCESS, FAIL, COMPLETE] export const UNIAPP_SERVICE_NVUE_ID = '__uniapp__service' -export function noop () { +export function noop() { } /** @@ -19,7 +19,7 @@ export function noop () { * @param {Object} args * @param {Object} extras */ -export function invokeVmMethodWithoutArgs (vm, method, args, extras) { +export function invokeVmMethodWithoutArgs(vm, method, args, extras) { if (!vm) { return } @@ -39,7 +39,7 @@ export function invokeVmMethodWithoutArgs (vm, method, args, extras) { * @param {Object} args * @param {Object} extras */ -export function invokeVmMethod (vm, method, args, extras) { +export function invokeVmMethod(vm, method, args, extras) { if (!vm) { return } @@ -50,36 +50,37 @@ export function invokeVmMethod (vm, method, args, extras) { return vm[method](pureArgs, normalizeCallback(method, callbacks)) } -export function findRefById (id, vm) { - return findRefByVNode(id, vm._vnode) +export function findElmById(id, vm) { + return findElmByVNode(id, vm._vnode) } -function findRefByVNode (id, vnode) { +function findElmByVNode(id, vnode) { if (!id || !vnode) { return } - if (vnode.data && - vnode.data.ref && - vnode.data.attrs && - vnode.data.attrs.id === id) { - return vnode.data.ref + if ( + vnode.data && + vnode.data.attrs && + vnode.data.attrs.id === id + ) { + return vnode.elm } const children = vnode.children if (!children) { return } for (let i = 0, len = children.length; i < len; i++) { - const ref = findRefByVNode(id, children[i]) - if (ref) { - return ref + const elm = findElmByVNode(id, children[i]) + if (elm) { + return elm } } } -function normalizeArgs (args = {}, extras) { +function normalizeArgs(args = {}, extras) { const callbacks = Object.create(null) - const iterator = function iterator (name) { + const iterator = function iterator(name) { const callback = args[name] if (isFn(callback)) { callbacks[name] = callback @@ -94,8 +95,8 @@ function normalizeArgs (args = {}, extras) { return [args, callbacks] } -function normalizeCallback (method, callbacks) { - return function weexCallback (ret) { +function normalizeCallback(method, callbacks) { + return function weexCallback(ret) { const type = ret.type delete ret.type const callback = callbacks[type] diff --git a/src/platforms/app-plus/service/api/constants.js b/src/platforms/app-plus/service/api/constants.js new file mode 100644 index 000000000..50f999af9 --- /dev/null +++ b/src/platforms/app-plus/service/api/constants.js @@ -0,0 +1,7 @@ +export const DEVICE_FREQUENCY = 200 +export const NETWORK_TYPES = ['unknown', 'none', 'ethernet', 'wifi', '2g', '3g', '4g'] + +export const MAP_ID = '__UNIAPP_MAP' + +export const TEMP_PATH_BASE = '_doc/uniapp_temp' +export const TEMP_PATH = `${TEMP_PATH_BASE}_${Date.now()}` diff --git a/src/platforms/app-plus/service/api/context/audio.js b/src/platforms/app-plus/service/api/context/audio.js new file mode 100644 index 000000000..397c258a1 --- /dev/null +++ b/src/platforms/app-plus/service/api/context/audio.js @@ -0,0 +1,148 @@ +import { + getRealPath +} from '../util' + +import { + publish +} from '../../bridge' + +let audios = {} + +const evts = ['play', 'canplay', 'ended', 'stop', 'waiting', 'seeking', 'seeked', 'pause'] + +const publishAudioStateChange = (state, res = {}) => publish('onAudioStateChange', Object.assign({ + state +}, res)) + +const initStateChage = audioId => { + const audio = audios[audioId] + if (!audio) { + return + } + if (!audio.initStateChage) { + audio.initStateChage = true + + audio.addEventListener('error', error => { + publishAudioStateChange('error', { + audioId, + errMsg: 'MediaError', + errCode: error.code + }) + }) + + evts.forEach(event => { + audio.addEventListener(event, () => { + // 添加 isStopped 属性是为了解决 安卓设备停止播放后获取播放进度不正确的问题 + if (event === 'play') { + audio.isStopped = false + } else if (event === 'stop') { + audio.isStopped = true + } + publishAudioStateChange(event, { + audioId + }) + }) + }) + } +} + +export function createAudioInstance () { + const audioId = `${Date.now()}${Math.random()}` + const audio = audios[audioId] = plus.audio.createPlayer('') + audio.src = '' + audio.volume = 1 + audio.startTime = 0 + return { + errMsg: 'createAudioInstance:ok', + audioId + } +} + +export function destroyAudioInstance ({ + audioId +}) { + if (audios[audioId]) { + audios[audioId].close() + delete audios[audioId] + } + return { + errMsg: 'destroyAudioInstance:ok', + audioId + } +} + +export function setAudioState ({ + audioId, + src, + startTime, + autoplay = false, + loop = false, + obeyMuteSwitch, + volume +}) { + const audio = audios[audioId] + if (audio) { + let style = { + loop, + autoplay + } + if (src) { + audio.src = style.src = getRealPath(src) + } + if (startTime) { + audio.startTime = style.startTime = startTime + } + if (typeof volume === 'number') { + audio.volume = style.volume = volume + } + audio.setStyles(style) + initStateChage(audioId) + } + return { + errMsg: 'setAudioState:ok' + } +} + +export function getAudioState ({ + audioId +}) { + const audio = audios[audioId] + if (!audio) { + return { + errMsg: 'getAudioState:fail' + } + } + let { + src, + startTime, + volume + } = audio + + return { + errMsg: 'getAudioState:ok', + duration: 1e3 * (audio.getDuration() || 0), + currentTime: audio.isStopped ? 0 : 1e3 * audio.getPosition(), + paused: audio.isPaused, + src, + volume, + startTime: 1e3 * startTime, + buffered: 1e3 * audio.getBuffered() + } +} + +export function operateAudio ({ + operationType, + audioId, + currentTime +}) { + const audio = audios[audioId] + const operationTypes = ['play', 'pause', 'stop'] + if (operationTypes.indexOf(operationType) >= 0) { + audio[operationType === operationTypes[0] && audio.isPaused ? 'resume' : operationType]() + } else if (operationType === 'seek') { + audio.seekTo(currentTime / 1e3) + } + return { + errMsg: 'operateAudio:ok' + } +} diff --git a/src/platforms/app-plus/service/api/context/background-audio.js b/src/platforms/app-plus/service/api/context/background-audio.js new file mode 100644 index 000000000..067f56e27 --- /dev/null +++ b/src/platforms/app-plus/service/api/context/background-audio.js @@ -0,0 +1,181 @@ +import { + getRealPath +} from '../util' + +import { + publish +} from '../../bridge' + +let audio + +const publishBackgroundAudioStateChange = (state, res = {}) => publish('onBackgroundAudioStateChange', Object.assign({ + state +}, res)) + +const events = ['play', 'pause', 'ended', 'stop'] + +function initMusic () { + if (audio) { + return + } + audio = plus.audio.createPlayer({ + autoplay: true, + backgroundControl: true + }) + audio.src = audio.title = audio.epname = audio.singer = audio.coverImgUrl = audio.webUrl = '' + audio.startTime = 0 + events.forEach(event => { + audio.addEventListener(event, () => { + // 添加 isStopped 属性是为了解决 安卓设备停止播放后获取播放进度不正确的问题 + if (event === 'play') { + audio.isStopped = false + } else if (event === 'stop') { + audio.isStopped = true + } + const eventName = `onMusic${event[0].toUpperCase() + event.substr(1)}` + publish(eventName, { + dataUrl: audio.src, + errMsg: `${eventName}:ok` + }) + publishBackgroundAudioStateChange(event, { + dataUrl: audio.src + }) + }) + }) + audio.addEventListener('waiting', () => { + publishBackgroundAudioStateChange('waiting', { + dataUrl: audio.src + }) + }) + audio.addEventListener('error', err => { + publish('onMusicError', { + dataUrl: audio.src, + errMsg: 'Error:' + err.message + }) + publishBackgroundAudioStateChange('error', { + dataUrl: audio.src, + errMsg: err.message, + errCode: err.code + }) + }) + audio.addEventListener('prev', () => publish('onBackgroundAudioPrev')) + audio.addEventListener('next', () => publish('onBackgroundAudioNext')) +} + +function setMusicState (args) { + initMusic() + const props = ['src', 'startTime', 'coverImgUrl', 'webUrl', 'singer', 'epname', 'title'] + const style = {} + Object.keys(args).forEach(key => { + if (props.indexOf(key) >= 0) { + let val = args[key] + if (key === props[0] && val) { + val = getRealPath(val) + } + audio[key] = style[key] = val + } + }) + audio.setStyles(style) +} + +function getAudio () { + return audio +} + +export function getMusicPlayerState () { + const audio = getAudio() + if (audio) { + return { + dataUrl: audio.src, + duration: audio.getDuration() || 0, + currentPosition: audio.getPosition(), + status: audio.isPaused ? 0 : 1, + downloadPercent: Math.round(100 * audio.getBuffered() / audio.getDuration()), + errMsg: `getMusicPlayerState:ok` + } + } + return { + status: 2, + errMsg: `getMusicPlayerState:ok` + } +} +export function operateMusicPlayer ({ + operationType, + dataUrl, + position, + api = 'operateMusicPlayer', + title, + coverImgUrl +}) { + const audio = getAudio() + var operationTypes = ['resume', 'pause', 'stop'] + if (operationTypes.indexOf(operationType) > 0) { + audio && audio[operationType]() + } else if (operationType === 'play') { + setMusicState({ + src: dataUrl, + startTime: position, + title, + coverImgUrl + }) + audio.play() + } else if (operationType === 'seek') { + audio && audio.seekTo(position) + } + return { + errMsg: `${api}:ok` + } +} +export function setBackgroundAudioState (args) { + setMusicState(args) + return { + errMsg: `setBackgroundAudioState:ok` + } +} +export function operateBackgroundAudio ({ + operationType, + src, + startTime, + currentTime +}) { + return operateMusicPlayer({ + operationType, + dataUrl: src, + position: startTime || currentTime || 0, + api: 'operateBackgroundAudio' + }) +} +export function getBackgroundAudioState () { + let data = { + duration: 0, + currentTime: 0, + paused: false, + src: '', + buffered: 0, + title: '', + epname: '', + singer: '', + coverImgUrl: '', + webUrl: '', + startTime: 0, + errMsg: `getBackgroundAudioState:ok` + } + const audio = getAudio() + if (audio) { + let newData = { + duration: audio.getDuration() || 0, + currentTime: audio.isStopped ? 0 : audio.getPosition(), + paused: audio.isPaused, + src: audio.src, + buffered: audio.getBuffered(), + title: audio.title, + epname: audio.epname, + singer: audio.singer, + coverImgUrl: audio.coverImgUrl, + webUrl: audio.webUrl, + startTime: audio.startTime + } + data = Object.assign(data, newData) + } + return data +} diff --git a/src/platforms/app-plus/service/api/context/map.js b/src/platforms/app-plus/service/api/context/map.js new file mode 100644 index 000000000..23fa1350d --- /dev/null +++ b/src/platforms/app-plus/service/api/context/map.js @@ -0,0 +1,90 @@ +import { + invoke +} from '../../bridge' + +export function getMapCenterLocation ({ + mapId +} = {}, callbackId) { + const nativeMap = plus.maps.getMapById(mapId + '') + if (nativeMap) { + nativeMap.getCurrentCenter((state, { + latitude, + longitude + } = {}) => { + if (state === 0) { + invoke(callbackId, { + latitude, + longitude, + errMsg: 'getMapCenterLocation:ok' + }) + } else { + invoke(callbackId, { + errMsg: 'getMapCenterLocation:fail:state[' + state + ']' + }) + } + }) + } else { + return { + errMsg: 'getMapCenterLocation:fail:地图[' + mapId + ']不存在' + } + } +} + +export function moveToMapLocation ({ + mapId +} = {}) { + const nativeMap = plus.maps.getMapById(mapId + '') + if (nativeMap) { + nativeMap.getUserLocation((state, { + latitude, + longitude + } = {}) => { + if (state === 0) { + nativeMap.setCenter(new plus.maps.Point(longitude, latitude)) + } + }) + } + return { + errMsg: 'moveToMapLocation:ok' + } +} + +export function getMapScale ({ + mapId +} = {}) { + const nativeMap = plus.maps.getMapById(mapId + '') + if (nativeMap) { + return { + scale: nativeMap.getZoom(), + errMsg: 'getMapScale:ok' + } + } + return { + errMsg: 'getMapScale:fail:地图[' + mapId + ']不存在' + } +} + +export function getMapRegion ({ + mapId +} = {}) { + const nativeMap = plus.maps.getMapById(mapId + '') + if (nativeMap) { + const bounds = nativeMap.getBounds() + const northeast = bounds.getNorthEase() + const southwest = bounds.getSouthWest() + return { + northeast: { + latitude: northeast.getLat(), + longitude: northeast.getLng() + }, + southwest: { + latitude: southwest.getLat(), + longitude: southwest.getLng() + }, + errMsg: 'getMapRegion:ok' + } + } + return { + errMsg: 'getMapRegion:fail:地图[' + mapId + ']不存在' + } +} diff --git a/src/platforms/app-plus/service/api/context/video.js b/src/platforms/app-plus/service/api/context/video.js new file mode 100644 index 000000000..57cdbd338 --- /dev/null +++ b/src/platforms/app-plus/service/api/context/video.js @@ -0,0 +1,39 @@ +export function operateVideoPlayer ({ + data, + videoPlayerId, + type +}) { + const nativeVideo = plus.video.getVideoPlayerById(videoPlayerId + '') + if (nativeVideo) { + switch (type) { + case 'play': + case 'pause': + case 'stop': + case 'requestFullScreen': + case 'exitFullScreen': + case 'seek': + case 'playbackRate': + case 'showStatusBar': + nativeVideo[type].apply(nativeVideo, data) + return { + errMsg: 'operateVideoPlayer:ok' + } + case 'sendDanmu': + nativeVideo.sendDanmu({ + text: data[0], + color: data[1] + }) + return { + errMsg: 'operateVideoPlayer:ok' + } + default: + return { + errMsg: 'operateVideoPlayer:fail:暂不支持[' + type + ']' + } + } + } else { + return { + errMsg: 'operateVideoPlayer:fail:视频组件[' + videoPlayerId + ']不存在' + } + } +} diff --git a/src/platforms/app-plus/service/api/device/accelerometer.js b/src/platforms/app-plus/service/api/device/accelerometer.js new file mode 100644 index 000000000..1786ce4c1 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/accelerometer.js @@ -0,0 +1,55 @@ +import { + DEVICE_FREQUENCY +} from '../constants' + +import { + getLastWebview +} from '../util' + +import { + publish +} from '../../bridge' + +let watchAccelerationId = false +let isWatchAcceleration = false + +const clearWatchAcceleration = () => { + if (watchAccelerationId) { + plus.accelerometer.clearWatch(watchAccelerationId) + watchAccelerationId = false + } +} + +export function enableAccelerometer ({ + enable +}) { + if (enable) { // 启用监听 + clearWatchAcceleration() + watchAccelerationId = plus.accelerometer.watchAcceleration((res) => { + publish('onAccelerometerChange', { + x: res.xAxis, + y: res.yAxis, + z: res.zAxis, + errMsg: 'enableAccelerometer:ok' + }) + }, (e) => { + publish('onAccelerometerChange', { + errMsg: 'enableAccelerometer:fail' + }) + }, { + frequency: DEVICE_FREQUENCY + }) + if (!isWatchAcceleration) { + isWatchAcceleration = true + const webview = getLastWebview() + if (webview) { + webview.addEventListener('close', clearWatchAcceleration) + } + } + } else { + clearWatchAcceleration() + } + return { + errMsg: 'enableAccelerometer:ok' + } +} diff --git a/src/platforms/app-plus/service/api/device/add-phone-contact.js b/src/platforms/app-plus/service/api/device/add-phone-contact.js new file mode 100644 index 000000000..4fdada5ae --- /dev/null +++ b/src/platforms/app-plus/service/api/device/add-phone-contact.js @@ -0,0 +1,230 @@ +import { + invoke +} from '../../bridge' + +export function addPhoneContact ({ + photoFilePath = '', + nickName, + lastName, + middleName, + firstName, + remark, + mobilePhoneNumber, + weChatNumber, + addressCountry, + addressState, + addressCity, + addressStreet, + addressPostalCode, + organization, + title, + workFaxNumber, + workPhoneNumber, + hostNumber, + email, + url, + workAddressCountry, + workAddressState, + workAddressCity, + workAddressStreet, + workAddressPostalCode, + homeFaxNumber, + homePhoneNumber, + homeAddressCountry, + homeAddressState, + homeAddressCity, + homeAddressStreet, + homeAddressPostalCode +} = {}, callbackId) { + plus.contacts.getAddressBook(plus.contacts.ADDRESSBOOK_PHONE, (addressbook) => { + const contact = addressbook.create() + const name = {} + if (lastName) { + name.familyName = lastName + } + if (firstName) { + name.givenName = firstName + } + if (middleName) { + name.middleName = middleName + } + contact.name = name + + if (nickName) { + contact.nickname = nickName + } + + if (photoFilePath) { + contact.photos = [{ + type: 'url', + value: photoFilePath + }] + } + + if (remark) { + contact.note = remark + } + + const mobilePhone = { + type: 'mobile' + } + + const workPhone = { + type: 'work' + } + + const companyPhone = { + type: 'company' + } + + const homeFax = { + type: 'home fax' + } + + const workFax = { + type: 'work fax' + } + + if (mobilePhoneNumber) { + mobilePhone.value = mobilePhoneNumber + } + + if (workPhoneNumber) { + workPhone.value = workPhoneNumber + } + + if (hostNumber) { + companyPhone.value = hostNumber + } + + if (homeFaxNumber) { + homeFax.value = homeFaxNumber + } + + if (workFaxNumber) { + workFax.value = workFaxNumber + } + + contact.phoneNumbers = [mobilePhone, workPhone, companyPhone, homeFax, workFax] + + if (email) { + contact.emails = [{ + type: 'home', + value: email + }] + } + + if (url) { + contact.urls = [{ + type: 'other', + value: url + }] + } + + const org = { + type: 'company' + } + + if (organization) { + org.name = organization + } + if (title) { + org.title = title + } + + if (weChatNumber) { + contact.ims = [{ + type: 'other', + value: weChatNumber + }] + } + + const defaultAddress = { + type: 'other', + preferred: true + } + + const homeAddress = { + type: 'home' + } + const companyAddress = { + type: 'company' + } + + if (addressCountry) { + defaultAddress.country = addressCountry + } + + if (addressState) { + defaultAddress.region = addressState + } + + if (addressCity) { + defaultAddress.locality = addressCity + } + + if (addressStreet) { + defaultAddress.streetAddress = addressStreet + } + + if (addressPostalCode) { + defaultAddress.postalCode = addressPostalCode + } + + if (homeAddressCountry) { + homeAddress.country = homeAddressCountry + } + + if (homeAddressState) { + homeAddress.region = homeAddressState + } + + if (homeAddressCity) { + homeAddress.locality = homeAddressCity + } + + if (homeAddressStreet) { + homeAddress.streetAddress = homeAddressStreet + } + + if (homeAddressPostalCode) { + homeAddress.postalCode = homeAddressPostalCode + } + + if (workAddressCountry) { + companyAddress.country = workAddressCountry + } + + if (workAddressState) { + companyAddress.region = workAddressState + } + + if (workAddressCity) { + companyAddress.locality = workAddressCity + } + + if (workAddressStreet) { + companyAddress.streetAddress = workAddressStreet + } + + if (workAddressPostalCode) { + companyAddress.postalCode = workAddressPostalCode + } + + contact.addresses = [defaultAddress, homeAddress, companyAddress] + + contact.save(() => { + invoke(callbackId, { + errMsg: 'addPhoneContact:ok' + }) + }, (e) => { + invoke(callbackId, { + errMsg: 'addPhoneContact:fail' + }) + }) + }, (e) => { + invoke(callbackId, { + errMsg: 'addPhoneContact:fail' + }) + }) +} diff --git a/src/platforms/app-plus/service/api/device/bluetooth.js b/src/platforms/app-plus/service/api/device/bluetooth.js new file mode 100644 index 000000000..e7f116a3f --- /dev/null +++ b/src/platforms/app-plus/service/api/device/bluetooth.js @@ -0,0 +1,141 @@ +import { + invoke, + publish +} from '../../bridge' + +/** + * 执行蓝牙相关方法 + */ +function bluetoothExec (method, callbackId, data = {}, beforeSuccess) { + var deviceId = data.deviceId + if (deviceId) { + data.deviceId = deviceId.toUpperCase() + } + var serviceId = data.serviceId + if (serviceId) { + data.serviceId = serviceId.toUpperCase() + } + + plus.bluetooth[method.replace('Changed', 'Change')](Object.assign(data, { + success (data) { + if (typeof beforeSuccess === 'function') { + beforeSuccess(data) + } + invoke(callbackId, Object.assign({}, data, { + errMsg: `${method}:ok`, + code: undefined, + message: undefined + })) + }, + fail (error = {}) { + invoke(callbackId, { + errMsg: `${method}:fail ${error.message || ''}`, + errCode: error.code || 0 + }) + } + })) +} +/** + * 监听蓝牙相关事件 + */ +function bluetoothOn (method, beforeSuccess) { + plus.bluetooth[method.replace('Changed', 'Change')](function (data) { + if (typeof beforeSuccess === 'function') { + beforeSuccess(data) + } + publish(method, Object.assign({}, data, { + code: undefined, + message: undefined + })) + }) + return true +} + +function checkDevices (data) { + data.devices = data.devices.map(device => { + var advertisData = device.advertisData + if (advertisData && typeof advertisData !== 'string') { + device.advertisData = wx.arrayBufferToBase64(advertisData) + } + return device + }) +} + +var onBluetoothAdapterStateChange +var onBluetoothDeviceFound +var onBLEConnectionStateChange +var onBLEConnectionStateChanged +var onBLECharacteristicValueChange + +export function openBluetoothAdapter (data, callbackId) { + onBluetoothAdapterStateChange = onBluetoothAdapterStateChange || bluetoothOn('onBluetoothAdapterStateChange') + bluetoothExec('openBluetoothAdapter', callbackId) +} + +export function closeBluetoothAdapter (data, callbackId) { + bluetoothExec('closeBluetoothAdapter', callbackId) +} + +export function getBluetoothAdapterState (data, callbackId) { + bluetoothExec('getBluetoothAdapterState', callbackId) +} + +export function startBluetoothDevicesDiscovery (data, callbackId) { + onBluetoothDeviceFound = onBluetoothDeviceFound || bluetoothOn('onBluetoothDeviceFound', checkDevices) + bluetoothExec('startBluetoothDevicesDiscovery', callbackId, data) +} + +export function stopBluetoothDevicesDiscovery (data, callbackId) { + bluetoothExec('stopBluetoothDevicesDiscovery', callbackId) +} + +export function getBluetoothDevices (data, callbackId) { + bluetoothExec('getBluetoothDevices', callbackId, {}, checkDevices) +} + +export function getConnectedBluetoothDevices (data, callbackId) { + bluetoothExec('getConnectedBluetoothDevices', callbackId, data) +} + +export function createBLEConnection (data, callbackId) { + onBLEConnectionStateChange = onBLEConnectionStateChange || bluetoothOn('onBLEConnectionStateChange') + onBLEConnectionStateChanged = onBLEConnectionStateChanged || bluetoothOn('onBLEConnectionStateChanged') + bluetoothExec('createBLEConnection', callbackId, data) +} + +export function closeBLEConnection (data, callbackId) { + bluetoothExec('closeBLEConnection', callbackId, data) +} + +export function getBLEDeviceServices (data, callbackId) { + bluetoothExec('getBLEDeviceServices', callbackId, data) +} + +export function getBLEDeviceCharacteristics (data, callbackId) { + bluetoothExec('getBLEDeviceCharacteristics', callbackId, data) +} + +export function notifyBLECharacteristicValueChange (data, callbackId) { + onBLECharacteristicValueChange = onBLECharacteristicValueChange || bluetoothOn('onBLECharacteristicValueChange', + data => { + data.value = wx.arrayBufferToBase64(data.value) + }) + bluetoothExec('notifyBLECharacteristicValueChange', callbackId, data) +} + +export function notifyBLECharacteristicValueChanged (data, callbackId) { + onBLECharacteristicValueChange = onBLECharacteristicValueChange || bluetoothOn('onBLECharacteristicValueChange', + data => { + data.value = wx.arrayBufferToBase64(data.value) + }) + bluetoothExec('notifyBLECharacteristicValueChanged', callbackId, data) +} + +export function readBLECharacteristicValue (data, callbackId) { + bluetoothExec('readBLECharacteristicValue', callbackId, data) +} + +export function writeBLECharacteristicValue (data, callbackId) { + data.value = wx.base64ToArrayBuffer(data.value) + bluetoothExec('writeBLECharacteristicValue', callbackId, data) +} diff --git a/src/platforms/app-plus/service/api/device/brightness.js b/src/platforms/app-plus/service/api/device/brightness.js new file mode 100644 index 000000000..483cfa197 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/brightness.js @@ -0,0 +1,24 @@ +export function getScreenBrightness () { + return { + errMsg: 'getScreenBrightness:ok', + value: plus.screen.getBrightness() + } +} + +export function setScreenBrightness ({ + value +} = {}) { + plus.screen.setBrightness(value) + return { + errMsg: 'setScreenBrightness:ok' + } +} + +export function setKeepScreenOn ({ + keepScreenOn +} = {}) { + plus.device.setWakelock(!!keepScreenOn) + return { + errMsg: 'setKeepScreenOn:ok' + } +} diff --git a/src/platforms/app-plus/service/api/device/clipboard.js b/src/platforms/app-plus/service/api/device/clipboard.js new file mode 100644 index 000000000..96c6827c3 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/clipboard.js @@ -0,0 +1,30 @@ +import { + invoke +} from '../../bridge' + +export function getClipboardData (options, callbackId) { + const clipboard = weex.requireModule('clipboard') + clipboard.getString(ret => { + if (ret.result === 'success') { + invoke(callbackId, { + data: ret.data, + errMsg: 'getClipboardData:ok' + }) + } else { + invoke(callbackId, { + data: ret.result, + errMsg: 'getClipboardData:fail' + }) + } + }) +} + +export function setClipboardData ({ + data +}) { + const clipboard = weex.requireModule('clipboard') + clipboard.setString(data) + return { + errMsg: 'setClipboardData:ok' + } +} diff --git a/src/platforms/app-plus/service/api/device/compass.js b/src/platforms/app-plus/service/api/device/compass.js new file mode 100644 index 000000000..87bcb3bb2 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/compass.js @@ -0,0 +1,55 @@ +import { + DEVICE_FREQUENCY +} from '../constants' + +import { + getLastWebview +} from '../util' + +import { + publish +} from '../../bridge' + +let watchOrientationId = false +let isWatchOrientation = false + +const clearWatchOrientation = () => { + if (watchOrientationId) { + plus.orientation.clearWatch(watchOrientationId) + watchOrientationId = false + } +} + +export function enableCompass ({ + enable +}) { + if (enable) { + clearWatchOrientation() + watchOrientationId = plus.orientation.watchOrientation((o) => { + publish('onCompassChange', { + direction: o.magneticHeading, + errMsg: 'enableCompass:ok' + }) + }, (e) => { + publish('onCompassChange', { + errMsg: 'enableCompass:fail' + }) + }, { + frequency: DEVICE_FREQUENCY + }) + if (!isWatchOrientation) { + isWatchOrientation = true + const webview = getLastWebview() + if (webview) { + webview.addEventListener('close', () => { + plus.orientation.clearWatch(watchOrientationId) + }) + } + } + } else { + clearWatchOrientation() + } + return { + errMsg: 'enableCompass:ok' + } +} diff --git a/src/platforms/app-plus/service/api/device/get-network-type.js b/src/platforms/app-plus/service/api/device/get-network-type.js new file mode 100644 index 000000000..1ff25f6a6 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/get-network-type.js @@ -0,0 +1,10 @@ +import { + NETWORK_TYPES +} from '../constants' + +export function getNetworkType () { + return { + errMsg: 'getNetworkType:ok', + networkType: NETWORK_TYPES[plus.networkinfo.getCurrentType()] + } +} diff --git a/src/platforms/app-plus/service/api/device/ibeacon.js b/src/platforms/app-plus/service/api/device/ibeacon.js new file mode 100644 index 000000000..4dad2ccaf --- /dev/null +++ b/src/platforms/app-plus/service/api/device/ibeacon.js @@ -0,0 +1,79 @@ +import { + invoke, + publish +} from '../../bridge' + +let beaconUpdateState = false + +export function onBeaconUpdate () { + if (!beaconUpdateState) { + plus.ibeacon.onBeaconUpdate(function (data) { + publish('onBeaconUpdated', data) + }) + beaconUpdateState = true + } +} + +let beaconServiceChangeState = false + +export function onBeaconServiceChange () { + if (!beaconServiceChangeState) { + plus.ibeacon.onBeaconServiceChange(function (data) { + publish('onBeaconServiceChange', data) + publish('onBeaconServiceChanged', data) + }) + beaconServiceChangeState = true + } +} + +export function getBeacons (params, callbackId) { + plus.ibeacon.getBeacons({ + success: (result) => { + invoke(callbackId, { + errMsg: 'getBeacons:ok', + beacons: result.beacons + }) + }, + fail: (error) => { + invoke(callbackId, { + errMsg: 'getBeacons:fail:' + error.message + }) + } + }) +} + +export function startBeaconDiscovery ({ + uuids, + ignoreBluetoothAvailable = false +}, callbackId) { + plus.ibeacon.startBeaconDiscovery({ + uuids, + ignoreBluetoothAvailable, + success: (result) => { + invoke(callbackId, { + errMsg: 'startBeaconDiscovery:ok', + beacons: result.beacons + }) + }, + fail: (error) => { + invoke(callbackId, { + errMsg: 'startBeaconDiscovery:fail:' + error.message + }) + } + }) +} + +export function stopBeaconDiscovery (params, callbackId) { + plus.ibeacon.stopBeaconDiscovery({ + success: (result) => { + invoke(callbackId, Object.assign(result, { + errMsg: 'stopBeaconDiscovery:ok' + })) + }, + fail: (error) => { + invoke(callbackId, { + errMsg: 'stopBeaconDiscovery:fail:' + error.message + }) + } + }) +} diff --git a/src/platforms/app-plus/service/api/device/make-phone-call.js b/src/platforms/app-plus/service/api/device/make-phone-call.js new file mode 100644 index 000000000..504066236 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/make-phone-call.js @@ -0,0 +1,8 @@ +export function makePhoneCall ({ + phoneNumber +} = {}) { + plus.device.dial(phoneNumber) + return { + errMsg: 'makePhoneCall:ok' + } +} diff --git a/src/platforms/app-plus/service/api/device/scan-code.js b/src/platforms/app-plus/service/api/device/scan-code.js new file mode 100644 index 000000000..b14998b96 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/scan-code.js @@ -0,0 +1,184 @@ +import { + getStatusBarStyle +} from '../util' + +import { + invoke +} from '../../bridge' + +import { + ANI_SHOW, + ANI_DURATION +} from '../../constants' + +import { + registerPlusMessage +} from '../../framework/plus-message' + +export const SCAN_ID = '__UNIAPP_SCAN' +export const SCAN_PATH = '_www/__uniappscan.html' + +const MESSAGE_TYPE = 'scanCode' + +export function scanCode ({ + onlyFromCamera = false, + scanType +}, callbackId) { + const barcode = plus.barcode + const SCAN_TYPES = { + 'qrCode': [ + barcode.QR, + barcode.AZTEC, + barcode.MAXICODE + ], + 'barCode': [ + barcode.EAN13, + barcode.EAN8, + barcode.UPCA, + barcode.UPCE, + barcode.CODABAR, + barcode.CODE128, + barcode.CODE39, + barcode.CODE93, + barcode.ITF, + barcode.RSS14, + barcode.RSSEXPANDED + ], + 'datamatrix': [barcode.DATAMATRIX], + 'pdf417': [barcode.PDF417] + } + + const SCAN_MAPS = { + [barcode.QR]: 'QR_CODE', + [barcode.EAN13]: 'EAN_13', + [barcode.EAN8]: 'EAN_8', + [barcode.DATAMATRIX]: 'DATA_MATRIX', + [barcode.UPCA]: 'UPC_A', + [barcode.UPCE]: 'UPC_E', + [barcode.CODABAR]: 'CODABAR', + [barcode.CODE39]: 'CODE_39', + [barcode.CODE93]: 'CODE_93', + [barcode.CODE128]: 'CODE_128', + [barcode.ITF]: 'CODE_25', + [barcode.PDF417]: 'PDF_417', + [barcode.AZTEC]: 'AZTEC', + [barcode.RSS14]: 'RSS_14', + [barcode.RSSEXPANDED]: 'RSSEXPANDED' + } + + const statusBarStyle = getStatusBarStyle() + const isDark = statusBarStyle !== 'light' + + let result + + let filters = [] + if (Array.isArray(scanType) && scanType.length) { + scanType.forEach(type => { // 暂不考虑去重 + const types = SCAN_TYPES[type] + if (types) { + filters = filters.concat(types) + } + }) + } + if (!filters.length) { + filters = filters.concat(SCAN_TYPES['qrCode']).concat(SCAN_TYPES['barCode']).concat(SCAN_TYPES['datamatrix']).concat( + SCAN_TYPES['pdf417']) + } + + const buttons = [] + if (!onlyFromCamera) { + buttons.push({ + 'float': 'right', + 'text': '相册', + 'fontSize': '17px', + 'width': '60px', + 'onclick': function () { + plus.gallery.pick(file => { + barcode.scan(file, (type, code) => { + if (isDark) { + plus.navigator.setStatusBarStyle('isDark') + } + webview.close('auto') + result = { + type, + code + } + }, () => { + plus.nativeUI.toast('识别失败') + }, filters) + }, err => { + if (err.code !== 12) { + plus.nativeUI.toast('选择失败') + } + }, { + multiple: false, + system: false + }) + } + }) + } + + const webview = plus.webview.create(SCAN_PATH, SCAN_ID, { + titleNView: { + autoBackButton: true, + type: 'float', + backgroundColor: 'rgba(0,0,0,0)', + titleColor: '#ffffff', + titleText: '扫码', + titleSize: '17px', + buttons + }, + popGesture: 'close', + backButtonAutoControl: 'close' + }, { + __uniapp_type: 'scan', + __uniapp_dark: isDark, + __uniapp_scan_type: filters, + 'uni-app': 'none' + }) + const waiting = plus.nativeUI.showWaiting() + webview.addEventListener('titleUpdate', () => { + webview.show(ANI_SHOW, ANI_DURATION, () => { + waiting.close() + }) + }) + webview.addEventListener('close', () => { + if (result && 'code' in result) { + invoke(callbackId, { + result: result.code, + scanType: SCAN_MAPS[result.type] || '', + charSet: 'utf8', + path: '', + errMsg: 'scanCode:ok' + }) + } else { + invoke(callbackId, { + errMsg: 'scanCode:fail cancel' + }) + } + }) + if (isDark) { // 状态栏前景色 + plus.navigator.setStatusBarStyle('light') + webview.addEventListener('popGesture', ({ + type, + result + }) => { + if (type === 'start') { + plus.navigator.setStatusBarStyle('dark') + } else if (type === 'end' && !result) { + plus.navigator.setStatusBarStyle('light') + } + }) + } + // fixed by hxy 注册扫码事件 + registerPlusMessage(MESSAGE_TYPE, function (res) { + if (res && !res.errMsg) { + result = res + } else { + const errMsg = res && res.errMsg ? ' ' + res.errMsg : '' + result = { + errMsg: 'scanCode:fail' + errMsg + } + } + }, false) +} diff --git a/src/platforms/app-plus/service/api/device/system.js b/src/platforms/app-plus/service/api/device/system.js new file mode 100644 index 000000000..91920a742 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/system.js @@ -0,0 +1,57 @@ +import { + isTabBarPage, + getLastWebview +} from '../util' + +import { + TABBAR_HEIGHT, + TITLEBAR_HEIGHT +} from '../../constants' + +import tabbar from '../../framework/tabbar' + +export function getSystemInfo (args) { + const platform = plus.os.name.toLowerCase() + const ios = platform === 'ios' + // 安卓 plus 接口获取的屏幕大小值不为整数,iOS js 获取的屏幕大小横屏时颠倒 + const screenWidth = plus.screen.resolutionWidth + const screenHeight = plus.screen.resolutionHeight + // 横屏时 iOS 获取的状态栏高度错误,进行纠正 + var landscape = Math.abs(plus.navigator.getOrientation()) === 90 + var statusBarHeight = plus.navigator.getStatusbarHeight() + if (ios && landscape) { + statusBarHeight = Math.min(20, statusBarHeight) + } + // 判断是否存在 titleNView + var titleNView + var webview = getLastWebview() + if (webview) { + let style = webview.getStyle() + if (style) { + titleNView = style && style.titleNView + titleNView = titleNView && titleNView.type === 'default' + } + } + return { + errMsg: 'getSystemInfo:ok', + brand: '', + model: plus.device.model, + pixelRatio: plus.screen.scale, + screenWidth, + screenHeight, + // 安卓端 webview 宽度有时比屏幕多 1px,相比取最小值 + // TODO screenWidth,screenHeight + windowWidth: screenWidth, + windowHeight: Math.min(screenHeight - (titleNView ? (statusBarHeight + TITLEBAR_HEIGHT) + : 0) - (isTabBarPage() && tabbar.visible ? TABBAR_HEIGHT : 0), screenHeight), + statusBarHeight, + language: plus.os.language, + system: plus.os.version, + version: plus.runtime.innerVersion, + fontSizeSetting: '', + platform, + SDKVersion: '', + windowTop: 0, + windowBottom: 0 + } +} diff --git a/src/platforms/app-plus/service/api/device/vibrate.js b/src/platforms/app-plus/service/api/device/vibrate.js new file mode 100644 index 000000000..37fd9b2d6 --- /dev/null +++ b/src/platforms/app-plus/service/api/device/vibrate.js @@ -0,0 +1,12 @@ +export function vibrateLong () { + plus.device.vibrate(400) + return { + errMsg: 'vibrateLong:ok' + } +} +export function vibrateShort () { + plus.device.vibrate(15) + return { + errMsg: 'vibrateShort:ok' + } +} diff --git a/src/platforms/app-plus/service/api/file/file.js b/src/platforms/app-plus/service/api/file/file.js new file mode 100644 index 000000000..dd7dbb57d --- /dev/null +++ b/src/platforms/app-plus/service/api/file/file.js @@ -0,0 +1,181 @@ +import { + getRealPath +} from '../util' + +import { + invoke +} from '../../bridge' + +const SAVED_DIR = 'uniapp_save' +const SAVE_PATH = `_doc/${SAVED_DIR}` +const REGEX_FILENAME = /^.*[/]/ + +function getSavedFileDir (success, fail) { + fail = fail || function () {} + plus.io.requestFileSystem(plus.io.PRIVATE_DOC, fs => { // 请求_doc fs + fs.root.getDirectory(SAVED_DIR, { // 获取文件保存目录对象 + create: true + }, dir => { + success(dir) + }, err => { + fail('目录[' + SAVED_DIR + ']创建失败' + err.message) + }) + }, err => { + fail('目录[_doc]读取失败' + err.message) + }) +} + +export function saveFile ({ + tempFilePath +} = {}, callbackId) { + let fileName = tempFilePath.replace(REGEX_FILENAME, '') + if (fileName) { + let extName = '' + if (~fileName.indexOf('.')) { + extName = '.' + fileName.split('.').pop() + } + + fileName = (+new Date()) + '' + extName + + plus.io.resolveLocalFileSystemURL(getRealPath(tempFilePath), entry => { // 读取临时文件 FileEntry + getSavedFileDir(dir => { + entry.copyTo(dir, fileName, () => { // 复制临时文件 FileEntry,为了避免把相册里的文件删除,使用 copy,微信中是要删除临时文件的 + const savedFilePath = SAVE_PATH + '/' + fileName + invoke(callbackId, { + errMsg: 'saveFile:ok', + savedFilePath + }) + }, err => { + invoke(callbackId, { + errMsg: 'saveFile:fail 保存文件[' + tempFilePath + + '] copyTo 失败:' + err.message + }) + }) + }, message => { + invoke(callbackId, { + errMsg: 'saveFile:fail ' + message + }) + }) + }, err => { + invoke(callbackId, { + errMsg: 'saveFile:fail 文件[' + tempFilePath + ']读取失败' + err.message + }) + }) + } else { + return { + errMsg: 'saveFile:fail 文件名[' + tempFilePath + ']不存在' + } + } +} + +export function getSavedFileList (options, callbackId) { + getSavedFileDir(entry => { + var reader = entry.createReader() + + var fileList = [] + reader.readEntries(entries => { + if (entries && entries.length) { + entries.forEach(entry => { + entry.getMetadata(meta => { + fileList.push({ + filePath: plus.io.convertAbsoluteFileSystem(entry.fullPath), + createTime: meta.modificationTime.getTime(), + size: meta.size + }) + if (fileList.length === entries.length) { + invoke(callbackId, { + errMsg: 'getSavedFileList:ok', + fileList + }) + } + }, error => { + invoke(callbackId, { + errMsg: 'getSavedFileList:fail ' + error.message + }) + }, false) + }) + } else { + invoke(callbackId, { + errMsg: 'getSavedFileList:ok', + fileList + }) + } + }, error => { + invoke(callbackId, { + errMsg: 'getSavedFileList:fail ' + error.message + }) + }) + }, message => { + invoke(callbackId, { + errMsg: 'getSavedFileList:fail ' + message + }) + }) +} + +export function getFileInfo ({ + filePath, + digestAlgorithm = 'md5' +} = {}, callbackId) { + // TODO 计算文件摘要 + plus.io.resolveLocalFileSystemURL(getRealPath(filePath), entry => { + entry.getMetadata(meta => { + invoke(callbackId, { + errMsg: 'getFileInfo:ok', + size: meta.size, + digestAlgorithm: '' + }) + }, err => { + invoke(callbackId, { + errMsg: 'getFileInfo:fail 文件[' + + filePath + + '] getMetadata 失败:' + err.message + }) + }) + }, err => { + invoke(callbackId, { + errMsg: 'getFileInfo:fail 文件[' + filePath + ']读取失败:' + err.message + }) + }) +} + +export function getSavedFileInfo ({ + filePath +} = {}, callbackId) { + plus.io.resolveLocalFileSystemURL(getRealPath(filePath), entry => { + entry.getMetadata(meta => { + invoke(callbackId, { + createTime: meta.modificationTime.getTime(), + size: meta.size, + errMsg: 'getSavedFileInfo:ok' + }) + }, error => { + invoke(callbackId, { + errMsg: 'getSavedFileInfo:fail ' + error.message + }) + }, false) + }, () => { + invoke(callbackId, { + errMsg: 'getSavedFileInfo:fail file not find' + }) + }) +} + +export function removeSavedFile ({ + filePath +} = {}, callbackId) { + plus.io.resolveLocalFileSystemURL(getRealPath(filePath), entry => { + entry.remove(() => { + invoke(callbackId, { + errMsg: 'removeSavedFile:ok' + }) + }, err => { + invoke(callbackId, { + errMsg: 'removeSavedFile:fail 文件[' + filePath + ']删除失败:' + err.message + }) + }) + }, () => { + invoke(callbackId, { + errMsg: 'removeSavedFile:fail file not find' + }) + }) +} diff --git a/src/platforms/app-plus/service/api/file/open-document.js b/src/platforms/app-plus/service/api/file/open-document.js new file mode 100644 index 000000000..1065c5434 --- /dev/null +++ b/src/platforms/app-plus/service/api/file/open-document.js @@ -0,0 +1,23 @@ +import { + getRealPath +} from '../util' + +import { + invoke +} from '../../bridge' + +export function openDocument ({ + filePath, + fileType +} = {}, callbackId) { + plus.io.resolveLocalFileSystemURL(getRealPath(filePath), entry => { + plus.runtime.openFile(getRealPath(filePath)) + invoke(callbackId, { + errMsg: 'openDocument:ok' + }) + }, err => { + invoke(callbackId, { + errMsg: 'openDocument:fail 文件[' + filePath + ']读取失败:' + err.message + }) + }) +} diff --git a/src/platforms/app-plus/service/api/index.js b/src/platforms/app-plus/service/api/index.js new file mode 100644 index 000000000..7b1c1f572 --- /dev/null +++ b/src/platforms/app-plus/service/api/index.js @@ -0,0 +1,57 @@ +export * from './context/audio' +export * from './context/background-audio' +export * from './context/map' +export * from './context/video' + +export * from './device/accelerometer' +export * from './device/add-phone-contact' +export * from './device/bluetooth' +export * from './device/brightness' +export * from './device/clipboard' +export * from './device/compass' +export * from './device/get-network-type' +export * from './device/ibeacon' +export * from './device/make-phone-call' +export * from './device/scan-code' +export * from './device/system' +export * from './device/vibrate' + +export * from './file/file' +export * from './file/open-document' + +export * from './location/choose-location' +export * from './location/get-location' +export * from './location/open-location' + +export * from './media/audio' +export * from './media/choose-image' +export * from './media/choose-video' +export * from './media/compress-image' +export * from './media/get-image-info' +export * from './media/preview-image' +export * from './media/recorder' +export * from './media/save-image-to-photos-album' +export * from './media/save-video-to-photos-album' + +export * from './network/download-file' +export * from './network/request' +export * from './network/socket' +export * from './network/upload-file' + +export * from './plugin/get-provider' +export * from './plugin/oauth' +export * from './plugin/payment' +export * from './plugin/push' +export * from './plugin/share' + +export * from './ui/keyboard' +export * from './ui/navigation-bar' +export * from './ui/popup' + +export { + startPullDownRefresh, + stopPullDownRefresh +} + from './ui/pull-down-refresh' + +export * from './ui/tab-bar' diff --git a/src/platforms/app-plus/service/api/location/choose-location.js b/src/platforms/app-plus/service/api/location/choose-location.js new file mode 100644 index 000000000..21c0973d7 --- /dev/null +++ b/src/platforms/app-plus/service/api/location/choose-location.js @@ -0,0 +1,82 @@ +import { + MAP_ID +} from '../constants' + +import { + invoke +} from '../../bridge' + +import { + ANI_DURATION +} from '../../constants' + +import { + registerPlusMessage +} from '../../framework/plus-message' + +const CHOOSE_LOCATION_PATH = '_www/__uniappchooselocation.html' + +export function chooseLocation (params, callbackId) { + const statusBarStyle = plus.navigator.getStatusBarStyle() + const webview = plus.webview.create( + CHOOSE_LOCATION_PATH, + MAP_ID, { + titleNView: { + autoBackButton: true, + backgroundColor: '#000000', + titleColor: '#ffffff', + titleText: '选择位置', + titleSize: '17px', + buttons: [{ + float: 'right', + text: '完成', + fontSize: '17px', + onclick: function () { + webview.evalJS('__chooseLocationConfirm__()') + } + }] + }, + popGesture: 'close', + scrollIndicator: 'none' + }, { + __uniapp_type: 'map', + __uniapp_statusbar_style: statusBarStyle, + 'uni-app': 'none' + } + ) + if (statusBarStyle === 'dark') { + plus.navigator.setStatusBarStyle('light') + webview.addEventListener('popGesture', ({ + type, + result + }) => { + if (type === 'start') { + plus.navigator.setStatusBarStyle('dark') + } else if (type === 'end' && !result) { + plus.navigator.setStatusBarStyle('light') + } + }) + } + + webview.show('slide-in-bottom', ANI_DURATION, () => { + webview.evalJS(`__chooseLocation__(${JSON.stringify(params)})`) + }) + + // fixed by hxy + registerPlusMessage('chooseLocation', function (res) { + if (res && !res.errMsg) { + invoke(callbackId, { + name: res.poiname, + address: res.poiaddress, + latitude: res.latlng.lat, + longitude: res.latlng.lng, + errMsg: 'chooseLocation:ok' + }) + } else { + const errMsg = res && res.errMsg ? ' ' + res.errMsg : '' + invoke(callbackId, { + errMsg: 'chooseLocation:fail' + errMsg + }) + } + }, false) +} diff --git a/src/platforms/app-plus/service/api/location/get-location.js b/src/platforms/app-plus/service/api/location/get-location.js new file mode 100644 index 000000000..973c5c83c --- /dev/null +++ b/src/platforms/app-plus/service/api/location/get-location.js @@ -0,0 +1,75 @@ +import { + wgs84togcj02, + gcj02towgs84 +} from '../util' + +import { + invoke +} from '../../bridge' + +function getLocationSuccess (type, position, callbackId) { + const coords = position.coords + if (type !== position.coordsType) { + if (process.env.NODE_ENV !== 'production') { + console.log( + `UNIAPP[location]:before[${position.coordsType}][lng:${ + coords.longitude + },lat:${coords.latitude}]` + ) + } + let coordArray + if (type === 'wgs84') { + coordArray = gcj02towgs84(coords.longitude, coords.latitude) + } else if (type === 'gcj02') { + coordArray = wgs84togcj02(coords.longitude, coords.latitude) + } + if (coordArray) { + coords.longitude = coordArray[0] + coords.latitude = coordArray[1] + if (process.env.NODE_ENV !== 'production') { + console.log( + `UNIAPP[location]:after[${type}][lng:${coords.longitude},lat:${ + coords.latitude + }]` + ) + } + } + } + + invoke(callbackId, { + type, + altitude: coords.altitude || 0, + latitude: coords.latitude, + longitude: coords.longitude, + speed: coords.speed, + accuracy: coords.accuracy, + address: position.address, + errMsg: 'getLocation:ok' + }) +} + +export function getLocation ({ + type = 'wgs84', + geocode = false, + altitude = false +} = {}, callbackId) { + plus.geolocation.getCurrentPosition( + position => { + getLocationSuccess(type, position, callbackId) + }, + e => { + // 坐标地址解析失败 + if (e.code === 1501) { + getLocationSuccess(type, e, callbackId) + return + } + + invoke(callbackId, { + errMsg: 'getLocation:fail ' + e.message + }) + }, { + geocode: geocode, + enableHighAccuracy: altitude + } + ) +} diff --git a/src/platforms/app-plus/service/api/location/open-location.js b/src/platforms/app-plus/service/api/location/open-location.js new file mode 100644 index 000000000..e5cf71c14 --- /dev/null +++ b/src/platforms/app-plus/service/api/location/open-location.js @@ -0,0 +1,52 @@ +import { + MAP_ID +} from '../constants' + +import { + ANI_SHOW, + ANI_DURATION +} from '../../constants' + +const OPEN_LOCATION_PATH = '_www/__uniappopenlocation.html' + +export function openLocation (params) { + const statusBarStyle = plus.navigator.getStatusBarStyle() + const webview = plus.webview.create( + OPEN_LOCATION_PATH, + MAP_ID, { + titleNView: { + autoBackButton: true, + titleColor: '#ffffff', + titleText: '', + titleSize: '17px', + type: 'transparent' + }, + popGesture: 'close', + scrollIndicator: 'none' + }, { + __uniapp_type: 'map', + __uniapp_statusbar_style: statusBarStyle, + 'uni-app': 'none' + } + ) + if (statusBarStyle === 'light') { + plus.navigator.setStatusBarStyle('dark') + webview.addEventListener('popGesture', ({ + type, + result + }) => { + if (type === 'start') { + plus.navigator.setStatusBarStyle('light') + } else if (type === 'end' && !result) { + plus.navigator.setStatusBarStyle('dark') + } + }) + } + webview.show(ANI_SHOW, ANI_DURATION, () => { + webview.evalJS(`__openLocation__(${JSON.stringify(params)})`) + }) + + return { + errMsg: 'openLocation:ok' + } +} diff --git a/src/platforms/app-plus/service/api/media/audio.js b/src/platforms/app-plus/service/api/media/audio.js new file mode 100644 index 000000000..58b6140bd --- /dev/null +++ b/src/platforms/app-plus/service/api/media/audio.js @@ -0,0 +1,107 @@ +import { + getRealPath +} from '../util' + +import { + invoke +} from '../../bridge' + +const RECORD_TIME = 60 * 60 * 1000 + +let recorder +let recordTimeout + +export function startRecord (args, callbackId) { + recorder && recorder.stop() + recorder = plus.audio.getRecorder() + recorder.record({ + filename: '_doc/audio/', + format: 'aac' + }, (res) => { + invoke(callbackId, { + errMsg: 'startRecord:ok', + tempFilePath: res + }) + }, (res) => { + invoke(callbackId, { + errMsg: 'startRecord:fail' + }) + }) + recordTimeout = setTimeout(() => { + recorder.stop() + recorder = false + }, RECORD_TIME) +} + +export function stopRecord () { + if (recorder) { + recordTimeout && clearTimeout(recordTimeout) + recorder.stop() + return { + errMsg: 'stopRecord:ok' + } + } + return { + errMsg: 'stopRecord:fail' + } +} + +let player +let playerFilePath +let playerStatus + +export function playVoice ({ + filePath +} = {}, callbackId) { + if (player && playerFilePath === filePath && playerStatus === 'pause') { // 如果是当前音频被暂停,则继续播放 + playerStatus = 'play' + player.play((res) => { + player = false + playerFilePath = false + playerStatus = false + invoke(callbackId, { + errMsg: 'playVoice:ok' + }) + }) + return { + errMsg: 'playVoice:ok' + } + } + if (player) { // 如果存在音频播放,则停止 + player.stop() + } + playerFilePath = filePath + playerStatus = 'play' + player = plus.audio.createPlayer(getRealPath(filePath)) + // 播放操作成功回调 + player.play((res) => { + player = false + playerFilePath = false + playerStatus = false + invoke(callbackId, { + errMsg: 'playVoice:ok' + }) + }) +} + +export function pauseVoice () { + if (player && playerStatus === 'play') { + player.pause() + playerStatus = 'pause' + } + return { + errMsg: 'pauseVoice:ok' + } +} + +export function stopVoice () { + if (player) { + player.stop() + player = false + playerFilePath = false + playerStatus = false + } + return { + errMsg: 'stopVoice:ok' + } +} diff --git a/src/platforms/app-plus/service/api/media/choose-image.js b/src/platforms/app-plus/service/api/media/choose-image.js new file mode 100644 index 000000000..47030f66a --- /dev/null +++ b/src/platforms/app-plus/service/api/media/choose-image.js @@ -0,0 +1,154 @@ +import { + TEMP_PATH +} from '../constants' + +import { + invoke +} from '../../bridge' + +/** + * 获取文件信息 + * @param {string} filePath 文件路径 + * @returns {Promise} 文件信息Promise + */ +function getFileInfo (filePath) { + return new Promise((resolve, reject) => { + plus.io.resolveLocalFileSystemURL(filePath, function (entry) { + entry.getMetadata(function (meta) { + resolve({ + size: meta.size + }) + }, reject, false) + }, reject) + }) +} + +const invokeChooseImage = function (callbackId, type, sizeType, tempFilePaths = []) { + if (!tempFilePaths.length) { + invoke(callbackId, { + code: sizeType, + errMsg: `chooseImage:${type}` + }) + return + } + var tempFiles = [] + // plus.zip.compressImage 压缩文件并发调用在iOS端容易出现问题(图像错误、闪退),改为队列执行 + tempFilePaths.reduce((promise, tempFilePath, index, array) => { + return promise + .then(() => { + return getFileInfo(tempFilePath) + }) + .then(fileInfo => { + var size = fileInfo.size + // 压缩阈值 0.5 兆 + var threshold = 1024 * 1024 * 0.5 + // 判断是否需要压缩 + if ((sizeType.indexOf('compressed') >= 0 && sizeType.indexOf('original') < 0) || ((( + sizeType.indexOf( + 'compressed') < 0 && sizeType.indexOf('original') < 0) || (sizeType + .indexOf('compressed') >= 0 && sizeType.indexOf( + 'original') >= 0)) && size > threshold)) { + return new Promise((resolve, reject) => { + var dstPath = TEMP_PATH + '/compressed/' + Date.now() + ( + tempFilePath.match(/\.\S+$/) || [''])[0] + plus.nativeUI.showWaiting() + plus.zip.compressImage({ + src: tempFilePath, + dst: dstPath, + overwrite: true + }, () => { + resolve(dstPath) + }, (error) => { + reject(error) + }) + }) + .then(dstPath => { + array[index] = tempFilePath = dstPath + return getFileInfo(tempFilePath) + }) + .then(fileInfo => { + return tempFiles.push({ + path: tempFilePath, + size: fileInfo.size + }) + }) + } + return tempFiles.push({ + path: tempFilePath, + size: size + }) + }) + }, Promise.resolve()) + .then(() => { + plus.nativeUI.closeWaiting() + invoke(callbackId, { + errMsg: `chooseImage:${type}`, + tempFilePaths, + tempFiles + }) + }).catch(() => { + plus.nativeUI.closeWaiting() + invoke(callbackId, { + errMsg: `chooseImage:${type}` + }) + }) +} +const openCamera = function (callbackId, sizeType) { + const camera = plus.camera.getCamera() + camera.captureImage(e => invokeChooseImage(callbackId, 'ok', sizeType, [e]), + e => invokeChooseImage(callbackId, 'fail', 1), { + filename: TEMP_PATH + '/camera/' + }) +} +const openAlbum = function (callbackId, sizeType, count) { + // TODO Android 需要拷贝到 temp 目录 + plus.gallery.pick(e => invokeChooseImage(callbackId, 'ok', sizeType, e.files.map(file => { + return file + })), e => { + invokeChooseImage(callbackId, 'fail', 2) + }, { + maximum: count, + multiple: true, + system: false, + filename: TEMP_PATH + '/gallery/' + }) +} + +export function chooseImage ({ + count = 9, + sizeType = ['original', 'compressed'], + sourceType = ['album', 'camera'] +} = {}, callbackId) { + let fallback = true + if (sourceType.length === 1) { + if (sourceType[0] === 'album') { + fallback = false + openAlbum(callbackId, sizeType, count) + } else if (sourceType[0] === 'camera') { + fallback = false + openCamera(callbackId, sizeType) + } + } + if (fallback) { + plus.nativeUI.actionSheet({ + cancel: '取消', + buttons: [{ + title: '拍摄' + }, { + title: '从手机相册选择' + }] + }, (e) => { + switch (e.index) { + case 0: + invokeChooseImage(callbackId, 'fail', 0) + break + case 1: + openCamera(callbackId, sizeType) + break + case 2: + openAlbum(callbackId, sizeType, count) + break + } + }) + } +} diff --git a/src/platforms/app-plus/service/api/media/choose-video.js b/src/platforms/app-plus/service/api/media/choose-video.js new file mode 100644 index 000000000..445e211bd --- /dev/null +++ b/src/platforms/app-plus/service/api/media/choose-video.js @@ -0,0 +1,97 @@ +import { + TEMP_PATH +} from '../constants' + +import { + invoke +} from '../../bridge' + +const invokeChooseVideo = function (callbackId, type, tempFilePath = '') { + let callbackResult = { + errMsg: `chooseVideo:${type}`, + tempFilePath: tempFilePath, + duration: 0, + size: 0, + height: 0, + width: 0 + } + + if (type !== 'ok') { + invoke(callbackId, callbackResult) + return + } + + plus.io.getVideoInfo({ + filePath: tempFilePath, + success (videoInfo) { + callbackResult.size = videoInfo.size + callbackResult.duration = videoInfo.duration + callbackResult.width = videoInfo.width + callbackResult.height = videoInfo.height + invoke(callbackId, callbackResult) + }, + fail () { + invoke(callbackId, callbackResult) + }, + complete () { + invoke(callbackId, callbackResult) + } + }) +} +const openCamera = function (callbackId, maxDuration, cameraIndex) { + const camera = plus.camera.getCamera() + camera.startVideoCapture(e => invokeChooseVideo(callbackId, 'ok', e), e => invokeChooseVideo( + callbackId, 'fail'), { + index: cameraIndex, + videoMaximumDuration: maxDuration, + filename: TEMP_PATH + '/camera/' + }) +} +const openAlbum = function (callbackId) { + plus.gallery.pick(e => { + invokeChooseVideo(callbackId, 'ok', e) + }, e => invokeChooseVideo(callbackId, 'fail'), { + filter: 'video', + system: false, + filename: TEMP_PATH + '/gallery/' + }) +} +export function chooseVideo ({ + sourceType = ['album', 'camera'], + maxDuration = 60, + camera = 'back' +} = {}, callbackId) { + let fallback = true + let cameraIndex = (camera === 'front') ? 2 : 1 + if (sourceType.length === 1) { + if (sourceType[0] === 'album') { + fallback = false + openAlbum(callbackId) + } else if (sourceType[0] === 'camera') { + fallback = false + openCamera(callbackId, maxDuration, cameraIndex) + } + } + if (fallback) { + plus.nativeUI.actionSheet({ + cancel: '取消', + buttons: [{ + title: '拍摄' + }, { + title: '从手机相册选择' + }] + }, e => { + switch (e.index) { + case 0: + invokeChooseVideo(callbackId, 'fail') + break + case 1: + openCamera(callbackId, maxDuration, cameraIndex) + break + case 2: + openAlbum(callbackId) + break + } + }) + } +} diff --git a/src/platforms/app-plus/service/api/media/compress-image.js b/src/platforms/app-plus/service/api/media/compress-image.js new file mode 100644 index 000000000..f31c38b8d --- /dev/null +++ b/src/platforms/app-plus/service/api/media/compress-image.js @@ -0,0 +1,28 @@ +import { + TEMP_PATH +} from '../constants' + +import { + invoke +} from '../../bridge' + +export function compressImage ({ + src, + quality +}, callbackId) { + var dst = TEMP_PATH + '/compressed/' + Date.now() + (src.match(/\.\S+$/) || [''])[0] + plus.zip.compressImage({ + src, + dst, + quality + }, () => { + invoke(callbackId, { + errMsg: `compressImage:ok`, + tempFilePath: dst + }) + }, () => { + invoke(callbackId, { + errMsg: `compressImage:fail` + }) + }) +} diff --git a/src/platforms/app-plus/service/api/media/get-image-info.js b/src/platforms/app-plus/service/api/media/get-image-info.js new file mode 100644 index 000000000..e037ad834 --- /dev/null +++ b/src/platforms/app-plus/service/api/media/get-image-info.js @@ -0,0 +1,23 @@ +import { + invoke +} from '../../bridge' + +export function getImageInfo ({ + src +} = {}, callbackId) { + // fixed by hxy + plus.io.getImageInfo({ + src, + success (imageInfo) { + invoke(callbackId, { + errMsg: 'getImageInfo:ok', + ...imageInfo + }) + }, + fail () { + invoke(callbackId, { + errMsg: 'getImageInfo:fail' + }) + } + }) +} diff --git a/src/platforms/app-plus/service/api/media/preview-image.js b/src/platforms/app-plus/service/api/media/preview-image.js new file mode 100644 index 000000000..141e06c16 --- /dev/null +++ b/src/platforms/app-plus/service/api/media/preview-image.js @@ -0,0 +1,84 @@ +import { + getRealPath +} from '../util' + +import { + publish +} from '../../bridge' + +export function previewImage ({ + current = 0, + background = '#000000', + indicator = 'number', + loop = false, + urls, + longPressActions +} = {}) { + urls = urls.map(url => getRealPath(url)) + + const index = Number(current) + if (isNaN(index)) { + current = urls.indexOf(getRealPath(current)) + current = current < 0 ? 0 : current + } else { + current = index + } + + plus.nativeUI.previewImage(urls, { + current, + background, + indicator, + loop, + onLongPress: function (res) { + let itemList = [] + let itemColor = '' + let title = '' + let hasLongPressActions = longPressActions && longPressActions.callbackId + if (!hasLongPressActions) { + itemList = ['保存相册'] + itemColor = '#000000' + title = '' + } else { + itemList = longPressActions.itemList ? longPressActions.itemList : [] + itemColor = longPressActions.itemColor ? longPressActions.itemColor : '#000000' + title = longPressActions.title ? longPressActions.title : '' + } + + const options = { + buttons: itemList.map(item => ({ + title: item, + color: itemColor + })), + cancel: '取消' + } + if (title) { + options.title = title + } + // if (plus.os.name === 'iOS') { + // options.cancel = '取消' + // } + plus.nativeUI.actionSheet(options, (e) => { + if (e.index > 0) { + if (hasLongPressActions) { + publish(longPressActions.callbackId, { + errMsg: 'showActionSheet:ok', + tapIndex: e.index - 1, + index: res.index + }) + return + } + plus.gallery.save(res.url, function (GallerySaveEvent) { + plus.nativeUI.toast('保存图片到相册成功') + }) + } else if (hasLongPressActions) { + publish(longPressActions.callbackId, { + errMsg: 'showActionSheet:fail cancel' + }) + } + }) + } + }) + return { + errMsg: 'previewImage:ok' + } +} diff --git a/src/platforms/app-plus/service/api/media/recorder.js b/src/platforms/app-plus/service/api/media/recorder.js new file mode 100644 index 000000000..86084d65d --- /dev/null +++ b/src/platforms/app-plus/service/api/media/recorder.js @@ -0,0 +1,77 @@ +import { + TEMP_PATH +} from '../constants' + +import { + publish +} from '../../bridge' + +let recorder +let recordTimeout + +const publishRecorderStateChange = (state, res = {}) => { + publish('onRecorderStateChange', Object.assign({ + state + }, res)) +} + +const Recorder = { + start ({ + duration = 60000, + sampleRate, + numberOfChannels, + encodeBitRate, + format = 'mp3', + frameSize, + audioSource = 'auto' + }, callbackId) { + if (recorder) { + return publishRecorderStateChange('start') + } + recorder = plus.audio.getRecorder() + recorder.record({ + format, + samplerate: sampleRate, + filename: TEMP_PATH + '/recorder/' + }, res => publishRecorderStateChange('stop', { + tempFilePath: res + }), err => publishRecorderStateChange('error', { + errMsg: err.message + })) + recordTimeout = setTimeout(() => { + Recorder.stop() + }, duration) + publishRecorderStateChange('start') + }, + stop () { + if (recorder) { + recorder.stop() + recorder = false + recordTimeout && clearTimeout(recordTimeout) + } + }, + pause () { + if (recorder) { + publishRecorderStateChange('error', { + errMsg: '暂不支持录音pause操作' + }) + } + }, + resume () { + if (recorder) { + publishRecorderStateChange('error', { + errMsg: '暂不支持录音resume操作' + }) + } + } +} + +export function operateRecorder ({ + operationType, + ...args +}, callbackId) { + Recorder[operationType](args) + return { + errMsg: 'operateRecorder:ok' + } +} diff --git a/src/platforms/app-plus/service/api/media/save-image-to-photos-album.js b/src/platforms/app-plus/service/api/media/save-image-to-photos-album.js new file mode 100644 index 000000000..a73b492ec --- /dev/null +++ b/src/platforms/app-plus/service/api/media/save-image-to-photos-album.js @@ -0,0 +1,21 @@ +import { + getRealPath +} from '../util' + +import { + invoke +} from '../../bridge' + +export function saveImageToPhotosAlbum ({ + filePath +} = {}, callbackId) { + plus.gallery.save(getRealPath(filePath), e => { + invoke(callbackId, { + errMsg: 'saveImageToPhotosAlbum:ok' + }) + }, e => { + invoke(callbackId, { + errMsg: 'saveImageToPhotosAlbum:fail' + }) + }) +} diff --git a/src/platforms/app-plus/service/api/media/save-video-to-photos-album.js b/src/platforms/app-plus/service/api/media/save-video-to-photos-album.js new file mode 100644 index 000000000..c04433b88 --- /dev/null +++ b/src/platforms/app-plus/service/api/media/save-video-to-photos-album.js @@ -0,0 +1,21 @@ +import { + getRealPath +} from '../util' + +import { + invoke +} from '../../bridge' + +export function saveVideoToPhotosAlbum ({ + filePath +} = {}, callbackId) { + plus.gallery.save(getRealPath(filePath), e => { + invoke(callbackId, { + errMsg: 'saveVideoToPhotosAlbum:ok' + }) + }, e => { + invoke(callbackId, { + errMsg: 'saveVideoToPhotosAlbum:fail' + }) + }) +} diff --git a/src/platforms/app-plus/service/api/network/download-file.js b/src/platforms/app-plus/service/api/network/download-file.js new file mode 100644 index 000000000..24ca6079f --- /dev/null +++ b/src/platforms/app-plus/service/api/network/download-file.js @@ -0,0 +1,90 @@ +import { + TEMP_PATH +} from '../constants' + +import { + publish +} from '../../bridge' + +let downloadTaskId = 0 +const downloadTasks = {} + +const publishStateChange = (res) => { + publish('onDownloadTaskStateChange', res) +} + +const createDownloadTaskById = function (downloadTaskId, { + url, + header +} = {}) { + const downloader = plus.downloader.createDownload(url, { + time: __uniConfig.networkTimeout.downloadFile ? __uniConfig.networkTimeout.downloadFile / 1000 : 120, + filename: TEMP_PATH + '/download/', + // 需要与其它平台上的表现保持一致,不走重试的逻辑。 + retry: 0, + retryInterval: 0 + }, (download, statusCode) => { + if (statusCode) { + publishStateChange({ + downloadTaskId, + state: 'success', + tempFilePath: download.filename, + statusCode + }) + } else { + publishStateChange({ + downloadTaskId, + state: 'fail', + statusCode + }) + } + }) + for (const name in header) { + if (header.hasOwnProperty(name)) { + downloader.setRequestHeader(name, header[name]) + } + } + downloader.addEventListener('statechanged', (download, status) => { + if (download.downloadedSize && download.totalSize) { + publishStateChange({ + downloadTaskId, + state: 'progressUpdate', + progress: parseInt(download.downloadedSize / download.totalSize * 100), + totalBytesWritten: download.downloadedSize, + totalBytesExpectedToWrite: download.totalSize + }) + } + }) + downloadTasks[downloadTaskId] = downloader + downloader.start() + return { + downloadTaskId, + errMsg: 'createDownloadTask:ok' + } +} + +export function operateDownloadTask ({ + downloadTaskId, + operationType +} = {}) { + const downloadTask = downloadTasks[downloadTaskId] + if (downloadTask && operationType === 'abort') { + delete downloadTasks[downloadTaskId] + downloadTask.abort() + publishStateChange({ + downloadTaskId, + state: 'fail', + errMsg: 'abort' + }) + return { + errMsg: 'operateDownloadTask:ok' + } + } + return { + errMsg: 'operateDownloadTask:fail' + } +} + +export function createDownloadTask (args) { + return createDownloadTaskById(++downloadTaskId, args) +} diff --git a/src/platforms/app-plus/service/api/network/request.js b/src/platforms/app-plus/service/api/network/request.js new file mode 100644 index 000000000..f595cfb91 --- /dev/null +++ b/src/platforms/app-plus/service/api/network/request.js @@ -0,0 +1,138 @@ +import { + publish +} from '../../bridge' + +const USER_AGENT = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_5 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13G36 MicroMessenger/6.5.1 NetType/WIFI Language/zh_CN' + +let requestTaskId = 0 +const requestTasks = {} + +const publishStateChange = res => { + publish('onRequestTaskStateChange', res) + delete requestTasks[requestTaskId] +} + +const parseResponseHeaders = headerStr => { + const headers = {} + if (!headerStr) { + return headers + } + const headerPairs = headerStr.split('\u000d\u000a') + for (let i = 0; i < headerPairs.length; i++) { + const headerPair = headerPairs[i] + const index = headerPair.indexOf('\u003a\u0020') + if (index > 0) { + const key = headerPair.substring(0, index) + const val = headerPair.substring(index + 2) + headers[key] = val + } + } + return headers +} + +export function createRequestTaskById (requestTaskId, { + url, + data, + header, + method = 'GET' +} = {}) { + let abortTimeout + let xhr + // fixed by hxy 始终使用 plus 的 XHR + xhr = new plus.net.XMLHttpRequest() + xhr.open(method, url, true) + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (abortTimeout) { + clearTimeout(abortTimeout) + } + xhr.onreadystatechange = null + const statusCode = xhr.status + if (statusCode) { + publishStateChange({ + requestTaskId, + state: 'success', + data: xhr.responseText, + statusCode, + header: parseResponseHeaders(xhr.getAllResponseHeaders()) + }) + } else { + publishStateChange({ + requestTaskId, + state: 'fail', + statusCode, + errMsg: 'abort' + }) + } + } + } + let hasContentType = false + for (const name in header) { + if (header.hasOwnProperty(name)) { + if (!hasContentType && name.toLowerCase() === 'content-type') { + hasContentType = true + xhr.setRequestHeader('Content-Type', header[name]) // 大小写必须一致,否则部分服务器会返回invalid header name + } else { + xhr.setRequestHeader(name, header[name]) + } + } + } + if (!hasContentType && method === 'POST') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8') + } + if (__uniConfig.crossDomain === true) { + xhr.setRequestHeader('User-Agent', USER_AGENT) + } + + if (__uniConfig.networkTimeout.request) { + abortTimeout = setTimeout(() => { + xhr.onreadystatechange = null + xhr.abort() + publishStateChange({ + requestTaskId, + state: 'fail', + data: xhr.responseText, + statusCode: 0, + errMsg: 'timeout' + }) + }, __uniConfig.networkTimeout.request) + } + + if (typeof data !== 'string' && method === 'GET') { + data = null + } + try { + xhr.send(data) + requestTasks[requestTaskId] = xhr + } catch (e) { + return { + requestTaskId, + errMsg: 'createRequestTask:fail' + } + } + return { + requestTaskId, + errMsg: 'createRequestTask:ok' + } +} + +export function createRequestTask (args) { + return createRequestTaskById(++requestTaskId, args) +} + +export function operateRequestTask ({ + requestTaskId, + operationType +} = {}) { + const requestTask = requestTasks[requestTaskId] + if (requestTask && operationType === 'abort') { + requestTask.abort() + return { + errMsg: 'operateRequestTask:ok' + } + } + return { + errMsg: 'operateRequestTask:fail' + } +} diff --git a/src/platforms/app-plus/service/api/network/socket.js b/src/platforms/app-plus/service/api/network/socket.js new file mode 100644 index 000000000..598fc1c82 --- /dev/null +++ b/src/platforms/app-plus/service/api/network/socket.js @@ -0,0 +1,93 @@ +import { + publish +} from '../../bridge' + +let socketTaskId = 0 +const socketTasks = {} + +const publishStateChange = (res) => { + publish('onSocketTaskStateChange', res) +} + +const createSocketTaskById = function (socketTaskId, { + url, + data, + header, + method, + protocols +} = {}) { + // fixed by hxy 需要测试是否支持 arraybuffer + const socket = weex.requireModule('webSocket') + socket.WebSocket(url, Array.isArray(protocols) ? protocols.join(',') : protocols) + // socket.binaryType = 'arraybuffer' + socketTasks[socketTaskId] = socket + + socket.onopen(function (e) { + publishStateChange({ + socketTaskId, + state: 'open' + }) + }) + socket.onmessage(function (e) { + publishStateChange({ + socketTaskId, + state: 'message', + data: e.data + }) + }) + socket.onerror(function (e) { + publishStateChange({ + socketTaskId, + state: 'error', + errMsg: e.message + }) + }) + socket.onclose(function (e) { + delete socketTasks[socketTaskId] + publishStateChange({ + socketTaskId, + state: 'close' + }) + }) + return { + socketTaskId, + errMsg: 'createSocketTask:ok' + } +} + +export function createSocketTask (args) { + return createSocketTaskById(++socketTaskId, args) +} + +export function operateSocketTask (args) { + const { + operationType, + code, + data, + socketTaskId + } = PlusNativeBuffer.unpack(args) + const socket = socketTasks[socketTaskId] + if (!socket) { + return { + errMsg: 'operateSocketTask:fail' + } + } + switch (operationType) { + case 'send': + if (data) { + socket.send(data) + } + return { + errMsg: 'operateSocketTask:ok' + } + case 'close': + socket.close(code) + delete socketTasks[socketTaskId] + return { + errMsg: 'operateSocketTask:ok' + } + } + return { + errMsg: 'operateSocketTask:fail' + } +} diff --git a/src/platforms/app-plus/service/api/network/upload-file.js b/src/platforms/app-plus/service/api/network/upload-file.js new file mode 100644 index 000000000..91d16d2da --- /dev/null +++ b/src/platforms/app-plus/service/api/network/upload-file.js @@ -0,0 +1,112 @@ +import { + getRealPath +} from '../util' + +import { + publish +} from '../../bridge' + +let uploadTaskId = 0 +const uploadTasks = {} + +const publishStateChange = (res) => { + publish('onUploadTaskStateChange', res) +} + +const createUploadTaskById = function (uploadTaskId, { + url, + filePath, + name, + files, + header, + formData +} = {}) { + const uploader = plus.uploader.createUpload(url, { + timeout: __uniConfig.networkTimeout.uploadFile ? __uniConfig.networkTimeout.uploadFile / 1000 : 120, + // 需要与其它平台上的表现保持一致,不走重试的逻辑。 + retry: 0, + retryInterval: 0 + }, (upload, statusCode) => { + if (statusCode) { + publishStateChange({ + uploadTaskId, + state: 'success', + data: upload.responseText, + statusCode + }) + } else { + publishStateChange({ + uploadTaskId, + state: 'fail', + data: '', + statusCode + }) + } + delete uploadTasks[uploadTaskId] + }) + + for (const name in header) { + if (header.hasOwnProperty(name)) { + uploader.setRequestHeader(name, header[name]) + } + } + for (const name in formData) { + if (formData.hasOwnProperty(name)) { + uploader.addData(name, formData[name]) + } + } + if (files && files.length) { + files.forEach(file => { + uploader.addFile(getRealPath(file.uri), { + key: file.name || 'file' + }) + }) + } else { + uploader.addFile(getRealPath(filePath), { + key: name + }) + } + uploader.addEventListener('statechanged', (upload, status) => { + if (upload.uploadedSize && upload.totalSize) { + publishStateChange({ + uploadTaskId, + state: 'progressUpdate', + progress: parseInt(upload.uploadedSize / upload.totalSize * 100), + totalBytesSent: upload.uploadedSize, + totalBytesExpectedToSend: upload.totalSize + }) + } + }) + uploadTasks[uploadTaskId] = uploader + uploader.start() + return { + uploadTaskId, + errMsg: 'createUploadTask:ok' + } +} + +export function operateUploadTask ({ + uploadTaskId, + operationType +} = {}) { + const uploadTask = uploadTasks[uploadTaskId] + if (uploadTask && operationType === 'abort') { + delete uploadTasks[uploadTaskId] + uploadTask.abort() + publishStateChange({ + uploadTaskId, + state: 'fail', + errMsg: 'abort' + }) + return { + errMsg: 'operateUploadTask:ok' + } + } + return { + errMsg: 'operateUploadTask:fail' + } +} + +export function createUploadTask (args) { + return createUploadTaskById(++uploadTaskId, args) +} diff --git a/src/platforms/app-plus/service/api/plugin/get-provider.js b/src/platforms/app-plus/service/api/plugin/get-provider.js new file mode 100644 index 000000000..0bdb45905 --- /dev/null +++ b/src/platforms/app-plus/service/api/plugin/get-provider.js @@ -0,0 +1,76 @@ +import { + invoke +} from '../../bridge' + +const providers = { + oauth (callback) { + plus.oauth.getServices(services => { + const provider = [] + services.forEach(({ + id + }) => { + provider.push(id) + }) + callback(null, provider) + }, err => { + callback(err) + }) + }, + share (callback) { + plus.share.getServices(services => { + const provider = [] + services.forEach(({ + id + }) => { + provider.push(id) + }) + callback(null, provider) + }, err => { + callback(err) + }) + }, + payment (callback) { + plus.payment.getChannels(services => { + const provider = [] + services.forEach(({ + id + }) => { + provider.push(id) + }) + callback(null, provider) + }, err => { + callback(err) + }) + }, + push (callback) { + if (typeof weex !== 'undefined' || typeof plus !== 'undefined') { + callback(null, [plus.push.getClientInfo().id]) + } else { + callback(null, []) + } + } +} + +export function getProvider ({ + service +}, callbackId) { + if (providers[service]) { + providers[service]((err, provider) => { + if (err) { + invoke(callbackId, { + errMsg: 'getProvider:fail:' + err.message + }) + } else { + invoke(callbackId, { + errMsg: 'getProvider:ok', + service, + provider + }) + } + }) + } else { + invoke(callbackId, { + errMsg: 'getProvider:fail:服务[' + service + ']不支持' + }) + } +} diff --git a/src/platforms/app-plus/service/api/plugin/oauth.js b/src/platforms/app-plus/service/api/plugin/oauth.js new file mode 100644 index 000000000..4e76d3875 --- /dev/null +++ b/src/platforms/app-plus/service/api/plugin/oauth.js @@ -0,0 +1,123 @@ +import { + invoke +} from '../../bridge' + +const loginServices = {} + +const loginByService = (provider, callbackId) => { + function login () { + loginServices[provider].login(res => { + const authResult = res.target.authResult + invoke(callbackId, { + code: authResult.code, + authResult: authResult, + errMsg: 'login:ok' + }) + }, err => { + invoke(callbackId, { + code: err.code, + errMsg: 'login:fail:' + err.message + }) + }) + } + // 先注销再登录 + loginServices[provider].logout(login, login) +} +/** + * 微信登录 + */ +export function login (params, callbackId) { + const provider = params.provider || 'weixin' + if (loginServices[provider]) { + loginByService(provider, callbackId) + } else { + plus.oauth.getServices(services => { + loginServices[provider] = services.find(({ + id + }) => id === provider) + if (!loginServices[provider]) { + invoke(callbackId, { + code: '', + errMsg: 'login:fail:登录服务[' + provider + ']不存在' + }) + } else { + loginByService(provider, callbackId) + } + }, err => { + invoke(callbackId, { + code: err.code, + errMsg: 'login:fail:' + err.message + }) + }) + } +} + +const getUserInfo = function (params, callbackId) { + const provider = params.provider || 'weixin' + const loginService = loginServices[provider] + if (!loginService || !loginService.authResult) { + return invoke(callbackId, { + errMsg: 'operateWXData:fail:请先调用 uni.login' + }) + } + loginService.getUserInfo(res => { + if (provider === 'weixin') { + const wechatUserInfo = loginService.userInfo + const userInfo = { + openId: wechatUserInfo.openid, + nickName: wechatUserInfo.nickname, + gender: wechatUserInfo.sex, + city: wechatUserInfo.city, + province: wechatUserInfo.province, + country: wechatUserInfo.country, + avatarUrl: wechatUserInfo.headimgurl, + unionId: wechatUserInfo.unionid + } + invoke(callbackId, { + errMsg: 'operateWXData:ok', + data: { + data: JSON.stringify(userInfo), + rawData: '', + signature: '', + encryptedData: '', + iv: '' + } + }) + } else { + loginService.userInfo.openId = loginService.userInfo.openId || loginService.userInfo.openid || + loginService.authResult.openid + loginService.userInfo.nickName = loginService.userInfo.nickName || loginService.userInfo.nickname + loginService.userInfo.avatarUrl = loginService.userInfo.avatarUrl || loginService.userInfo.avatarUrl || + loginService.userInfo.headimgurl + invoke(callbackId, { + errMsg: 'operateWXData:ok', + data: { + data: JSON.stringify(loginService.userInfo), + rawData: '', + signature: '', + encryptedData: '', + iv: '' + } + }) + } + }, err => { + invoke(callbackId, { + errMsg: 'operateWXData:fail:' + err.message + }) + }) +} + +/** + * 获取用户信息 + */ +export function operateWXData (params, callbackId) { + switch (params.data.api_name) { + case 'webapi_getuserinfo': + getUserInfo(params, callbackId) + break + default: + return { + errMsg: 'operateWXData:fail' + } + } +} diff --git a/src/platforms/app-plus/service/api/plugin/payment.js b/src/platforms/app-plus/service/api/plugin/payment.js new file mode 100644 index 000000000..39ebab11c --- /dev/null +++ b/src/platforms/app-plus/service/api/plugin/payment.js @@ -0,0 +1,31 @@ +import { + invoke +} from '../../bridge' + +export function requestPayment (params, callbackId) { + const provider = params.provider + plus.payment.getChannels(services => { + const service = services.find(({ + id + }) => id === provider) + if (!service) { + invoke(callbackId, { + errMsg: 'requestPayment:fail:支付服务[' + provider + ']不存在' + }) + } else { + plus.payment.request(service, params.orderInfo, res => { + invoke(callbackId, { + errMsg: 'requestPayment:ok' + }) + }, err => { + invoke(callbackId, { + errMsg: 'requestPayment:fail:' + err.message + }) + }) + } + }, err => { + invoke(callbackId, { + errMsg: 'requestPayment:fail:' + err.message + }) + }) +} diff --git a/src/platforms/app-plus/service/api/plugin/push.js b/src/platforms/app-plus/service/api/plugin/push.js new file mode 100644 index 000000000..f4ed098dc --- /dev/null +++ b/src/platforms/app-plus/service/api/plugin/push.js @@ -0,0 +1,65 @@ +import { + publish +} from '../../bridge' + +let onPushing + +let isListening = false + +let unsubscribe = false + +export function subscribePush (params, callbackId) { + const clientInfo = plus.push.getClientInfo() + if (clientInfo) { + if (!isListening) { + isListening = true + plus.push.addEventListener('receive', msg => { + if (onPushing && !unsubscribe) { + publish('onPushMessage', { + messageId: msg.__UUID__, + data: msg.payload, + errMsg: 'onPush:ok' + }) + } + }) + } + unsubscribe = false + clientInfo.errMsg = 'subscribePush:ok' + return clientInfo + } else { + return { + errMsg: 'subscribePush:fail:请确保当前运行环境已包含 push 模块' + } + } +} + +export function unsubscribePush (params) { + unsubscribe = true + return { + errMsg: 'unsubscribePush:ok' + } +} + +export function onPush () { + if (!isListening) { + return { + errMsg: 'onPush:fail:请先调用 uni.subscribePush' + } + } + if (plus.push.getClientInfo()) { + onPushing = true + return { + errMsg: 'onPush:ok' + } + } + return { + errMsg: 'onPush:fail:请确保当前运行环境已包含 push 模块' + } +} + +export function offPush (params) { + onPushing = false + return { + errMsg: 'offPush:ok' + } +} diff --git a/src/platforms/app-plus/service/api/plugin/share.js b/src/platforms/app-plus/service/api/plugin/share.js new file mode 100644 index 000000000..b40b6354b --- /dev/null +++ b/src/platforms/app-plus/service/api/plugin/share.js @@ -0,0 +1,197 @@ +import { + TEMP_PATH +} from '../constants' + +import { + getRealPath +} from '../util' + +import { + invoke +} from '../../bridge' + +// 0:图文,1:纯文字,2:纯图片,3:音乐,4:视频,5:小程序 +const TYPES = { + '0': { + name: 'web', + title: '图文' + }, + '1': { + name: 'text', + title: '纯文字' + }, + '2': { + name: 'image', + title: '纯图片' + }, + '3': { + name: 'music', + title: '音乐' + }, + '4': { + name: 'video', + title: '视频' + }, + '5': { + name: 'miniProgram', + title: '小程序' + } +} + +const parseParams = (args, callbackId, method) => { + args.type = args.type || 0 + + let { + provider, + type, + title, + summary: content, + href, + imageUrl, + mediaUrl: media, + scene, + miniProgram + } = args + + if (typeof imageUrl === 'string' && imageUrl) { + imageUrl = getRealPath(imageUrl) + } + + const shareType = TYPES[type + ''] + if (shareType) { + let sendMsg = { + provider, + type: shareType.name, + title, + content, + href, + pictures: [imageUrl], + thumbs: [imageUrl], + media, + miniProgram, + extra: { + scene + } + } + if (provider === 'weixin' && (type === 1 || type === 2)) { + delete sendMsg.thumbs + } + return sendMsg + } + return '分享参数 type 不正确' +} + +const sendShareMsg = function (service, params, callbackId, method = 'share') { + service.send( + params, + () => { + invoke(callbackId, { + errMsg: method + ':ok' + }) + }, + err => { + invoke(callbackId, { + errMsg: method + ':fail:' + err.message + }) + } + ) +} + +export function shareAppMessageDirectly ({ + title, + path, + imageUrl, + useDefaultSnapshot +}, callbackId) { + title = title || __uniConfig.appname + const goShare = () => { + share({ + provider: 'weixin', + type: 0, + title, + imageUrl, + href: path, + scene: 'WXSceneSession' + }, + callbackId, + 'shareAppMessageDirectly' + ) + } + if (useDefaultSnapshot) { + const pages = getCurrentPages() + const webview = plus.webview.getWebviewById(pages[pages.length - 1].__wxWebviewId__ + '') + if (webview) { + const bitmap = new plus.nativeObj.Bitmap() + webview.draw( + bitmap, + () => { + const fileName = TEMP_PATH + '/share/snapshot.jpg' + bitmap.save( + fileName, { + overwrite: true, + format: 'jpg' + }, + () => { + imageUrl = fileName + goShare() + }, + err => { + invoke(callbackId, { + errMsg: 'shareAppMessageDirectly:fail:' + err.message + }) + } + ) + }, + err => { + invoke(callbackId, { + errMsg: 'shareAppMessageDirectly:fail:' + err.message + }) + } + ) + } else { + goShare() + } + } else { + goShare() + } +} + +export function share (params, callbackId, method = 'share') { + params = parseParams(params, callbackId, method) + if (typeof params === 'string') { + return invoke(callbackId, { + errMsg: method + ':fail:' + params + }) + } + const provider = params.provider + plus.share.getServices( + services => { + const service = services.find(({ + id + }) => id === provider) + if (!service) { + invoke(callbackId, { + errMsg: method + ':fail:分享服务[' + provider + ']不存在' + }) + } else { + if (service.authenticated) { + sendShareMsg(service, params, callbackId) + } else { + service.authorize( + () => sendShareMsg(service, params, callbackId), + err => { + invoke(callbackId, { + errMsg: method + ':fail:' + err.message + }) + } + ) + } + } + }, + err => { + invoke(callbackId, { + errMsg: method + ':fail:' + err.message + }) + } + ) +} diff --git a/src/platforms/app-plus/service/api/ui/keyboard.js b/src/platforms/app-plus/service/api/ui/keyboard.js new file mode 100644 index 000000000..3c05ec355 --- /dev/null +++ b/src/platforms/app-plus/service/api/ui/keyboard.js @@ -0,0 +1,13 @@ +export function showKeyboard () { + plus.key.showSoftKeybord() + return { + errMsg: 'showKeyboard:ok' + } +} + +export function hideKeyboard () { + plus.key.hideSoftKeybord() + return { + errMsg: 'hideKeyboard:ok' + } +} diff --git a/src/platforms/app-plus/service/api/ui/navigation-bar.js b/src/platforms/app-plus/service/api/ui/navigation-bar.js new file mode 100644 index 000000000..329963066 --- /dev/null +++ b/src/platforms/app-plus/service/api/ui/navigation-bar.js @@ -0,0 +1,70 @@ +import { + getLastWebview +} from '../util' + +export function setNavigationBarTitle ({ + title = '' +} = {}) { + const webview = getLastWebview() + if (webview) { + const style = webview.getStyle() + if (style && style.titleNView) { + webview.setStyle({ + titleNView: { + titleText: title + } + }) + } + return { + errMsg: 'setNavigationBarTitle:ok' + } + } + return { + errMsg: 'setNavigationBarTitle:fail' + } +} + +export function showNavigationBarLoading () { + plus.nativeUI.showWaiting('', { + modal: false + }) + return { + errMsg: 'showNavigationBarLoading:ok' + } +} + +export function hideNavigationBarLoading () { + plus.nativeUI.closeWaiting() + return { + errMsg: 'hideNavigationBarLoading:ok' + } +} + +export function setNavigationBarColor ({ + frontColor, + backgroundColor +} = {}) { + const webview = getLastWebview() + if (webview) { + const styles = {} + if (frontColor) { + styles.titleColor = frontColor + } + if (backgroundColor) { + styles.backgroundColor = backgroundColor + } + plus.navigator.setStatusBarStyle(frontColor === '#000000' ? 'dark' : 'light') + const style = webview.getStyle() + if (style && style.titleNView) { + webview.setStyle({ + titleNView: styles + }) + } + return { + errMsg: 'setNavigationBarColor:ok' + } + } + return { + errMsg: 'setNavigationBarColor:fail' + } +} diff --git a/src/platforms/app-plus/service/api/ui/popup.js b/src/platforms/app-plus/service/api/ui/popup.js new file mode 100644 index 000000000..4e5868d62 --- /dev/null +++ b/src/platforms/app-plus/service/api/ui/popup.js @@ -0,0 +1,170 @@ +import { + invoke +} from '../../bridge' + +let waiting +let waitingTimeout +let toast = false +let toastTimeout + +export function showToast ({ + title = '', + icon = 'success', + image = '', + duration = 1500, + mask = false, + position = '' +} = {}) { + if (position) { + if (toast) { + toastTimeout && clearTimeout(toastTimeout) + plus.nativeUI.closeToast() + } + if (waiting) { + waitingTimeout && clearTimeout(waitingTimeout) + waiting.close() + } + if (~['top', 'center', 'bottom'].indexOf(position)) { + let richText = `${title}` + plus.nativeUI.toast(richText, { + verticalAlign: position, + type: 'richtext' + }) + toast = true + toastTimeout = setTimeout(() => { + hideToast() + }, 2000) + return + } + console.warn('uni.showToast 传入的 "position" 值 "' + position + '" 无效') + } + + if (duration) { + if (waiting) { + waitingTimeout && clearTimeout(waitingTimeout) + waiting.close() + } + if (toast) { + toastTimeout && clearTimeout(toastTimeout) + plus.nativeUI.closeToast() + } + if (icon && !~['success', 'loading', 'none'].indexOf(icon)) { + icon = 'success' + } + const waitingOptions = { + modal: mask, + back: 'transmit', + padding: '10px', + size: '16px' // 固定字体大小 + } + if (!image && (!icon || icon === 'none')) { // 无图 + // waitingOptions.width = '120px' + // waitingOptions.height = '40px' + waitingOptions.loading = { + display: 'none' + } + } else { // 有图 + waitingOptions.width = '140px' + waitingOptions.height = '112px' + } + if (image) { + waitingOptions.loading = { + display: 'block', + height: '55px', + icon: image, + interval: duration + } + } else { + if (icon === 'success') { + waitingOptions.loading = { + display: 'block', + height: '55px', + icon: '__uniappsuccess.png', + interval: duration + + } + } + } + + waiting = plus.nativeUI.showWaiting(title, waitingOptions) + waitingTimeout = setTimeout(() => { + hideToast() + }, duration) + } + return { + errMsg: 'showToast:ok' + } +} + +export function hideToast () { + if (toast) { + toastTimeout && clearTimeout(toastTimeout) + plus.nativeUI.closeToast() + toast = false + } + if (waiting) { + waitingTimeout && clearTimeout(waitingTimeout) + waiting.close() + waiting = null + waitingTimeout = null + } + return { + errMsg: 'hideToast:ok' + } +} +export function showModal ({ + title = '', + content = '', + showCancel = true, + cancelText = '取消', + cancelColor = '#000000', + confirmText = '确定', + confirmColor = '#3CC51F' +} = {}, callbackId) { + plus.nativeUI.confirm(content, (e) => { + if (showCancel) { + invoke(callbackId, { + errMsg: 'showModal:ok', + confirm: e.index === 1, + cancel: e.index === 0 || e.index === -1 + }) + } else { + invoke(callbackId, { + errMsg: 'showModal:ok', + confirm: e.index === 0, + cancel: false + }) + } + }, title, showCancel ? [cancelText, confirmText] : [confirmText]) +} +export function showActionSheet ({ + itemList = [], + itemColor = '#000000', + title = '' +}, callbackId) { + const options = { + buttons: itemList.map(item => ({ + title: item + })) + } + if (title) { + options.title = title + } + + if (plus.os.name === 'iOS') { + options.cancel = '取消' + } + + plus.nativeUI.actionSheet(options, (e) => { + if (e.index > 0) { + invoke(callbackId, { + errMsg: 'showActionSheet:ok', + tapIndex: e.index - 1 + }) + } else { + invoke(callbackId, { + errMsg: 'showActionSheet:fail cancel' + }) + } + }) +} diff --git a/src/platforms/app-plus/service/api/ui/pull-down-refresh.js b/src/platforms/app-plus/service/api/ui/pull-down-refresh.js new file mode 100644 index 000000000..b2242a427 --- /dev/null +++ b/src/platforms/app-plus/service/api/ui/pull-down-refresh.js @@ -0,0 +1,38 @@ +import { + getLastWebview +} from '../util' + +let webview + +export function setPullDownRefreshWebview (pullDownRefreshWebview) { + webview = pullDownRefreshWebview +} + +export function startPullDownRefresh () { + if (webview) { + webview.endPullToRefresh() + } + webview = getLastWebview() + if (webview) { + webview.beginPullToRefresh() + return { + errMsg: 'startPullDownRefresh:ok' + } + } + return { + errMsg: 'startPullDownRefresh:fail' + } +} + +export function stopPullDownRefresh () { + if (webview) { + webview.endPullToRefresh() + webview = null + return { + errMsg: 'stopPullDownRefresh:ok' + } + } + return { + errMsg: 'stopPullDownRefresh:fail' + } +} diff --git a/src/platforms/app-plus/service/api/ui/tab-bar.js b/src/platforms/app-plus/service/api/ui/tab-bar.js new file mode 100644 index 000000000..b7fdc29a4 --- /dev/null +++ b/src/platforms/app-plus/service/api/ui/tab-bar.js @@ -0,0 +1,83 @@ +import { + isTabBarPage +} from '../util' + +import tabbar from '../../framework/tabbar' + +export function setTabBarBadge ({ + index, + text, + type +}) { + tabbar.setTabBarBadge(type, index, text) + return { + errMsg: 'setTabBarBadge:ok' + } +} + +export function setTabBarItem ({ + index, + text, + iconPath, + selectedIconPath +}) { + if (!isTabBarPage()) { + return { + errMsg: 'setTabBarItem:fail not TabBar page' + } + } + tabbar.setTabBarItem(index, text, iconPath, selectedIconPath) + return { + errMsg: 'setTabBarItem:ok' + } +} + +export function setTabBarStyle ({ + color, + selectedColor, + backgroundColor, + borderStyle +}) { + if (!isTabBarPage()) { + return { + errMsg: 'setTabBarStyle:fail not TabBar page' + } + } + tabbar.setTabBarStyle({ + color, + selectedColor, + backgroundColor, + borderStyle + }) + return { + errMsg: 'setTabBarStyle:ok' + } +} + +export function hideTabBar ({ + animation +}) { + if (!isTabBarPage()) { + return { + errMsg: 'hideTabBar:fail not TabBar page' + } + } + tabbar.hideTabBar(animation) + return { + errMsg: 'hideTabBar:ok' + } +} + +export function showTabBar ({ + animation +}) { + if (!isTabBarPage()) { + return { + errMsg: 'showTabBar:fail not TabBar page' + } + } + tabbar.showTabBar(animation) + return { + errMsg: 'showTabBar:ok' + } +} diff --git a/src/platforms/app-plus/service/api/util.js b/src/platforms/app-plus/service/api/util.js new file mode 100644 index 000000000..b3a0fd42d --- /dev/null +++ b/src/platforms/app-plus/service/api/util.js @@ -0,0 +1,167 @@ +export function getLastWebview () { + try { + const pages = getCurrentPages() + if (pages.length) { + return pages[pages.length - 1].$getAppWebview() + } + } catch (e) { + if (process.env.NODE_ENV !== 'production') { + console.log('getCurrentPages is not ready') + } + } +} + +export function isTabBarPage (route = '') { + if (!(__uniConfig.tabBar && Array.isArray(__uniConfig.tabBar.list))) { + return false + } + try { + if (!route) { + const pages = getCurrentPages() + if (!pages.length) { + return false + } + const page = pages[pages.length - 1] + if (!page) { + return false + } + route = page.route + } + return !!__uniConfig.tabBar.list.find(tabBarPage => { + const pagePath = tabBarPage.pagePath + return pagePath === route || pagePath === (route + '.html') + }) + } catch (e) { + if (process.env.NODE_ENV !== 'production') { + console.log('getCurrentPages is not ready') + } + } + return false +} + +const getRealRoute = (e, t) => { + if (t.indexOf('./') === 0) return getRealRoute(e, t.substr(2), !1) + let n + let i + let o = t.split('/') + for (n = 0, i = o.length; n < i && o[n] === '..'; n++); + o.splice(0, n) + t = o.join('/') + let r = e.length > 0 ? e.split('/') : [] + r.splice(r.length - n - 1, n + 1) + return r.concat(o).join('/') +} + +// 处理 Android 平台解压与非解压模式下获取的路径不一致的情况 +const _handleLocalPath = filePath => { + let localUrl = plus.io.convertLocalFileSystemURL(filePath) + return localUrl.replace(/^\/?apps\//, '/android_asset/apps/').replace(/\/$/, '') +} + +export function getRealPath (filePath) { + const SCHEME_RE = /^([a-z-]+:)?\/\//i + const BASE64_RE = /^data:[a-z-]+\/[a-z-]+;base64,/ + + // 无协议的情况补全 https + if (filePath.indexOf('//') === 0) { + filePath = 'https:' + filePath + } + + // 网络资源或base64 + if (SCHEME_RE.test(filePath) || BASE64_RE.test(filePath)) { + return filePath + } + + if (filePath.indexOf('_www') === 0 || filePath.indexOf('_doc') === 0 || filePath.indexOf('_documents') === 0 || + filePath.indexOf('_downloads') === 0) { + return 'file://' + _handleLocalPath(filePath) + } + const wwwPath = 'file://' + _handleLocalPath('_www') + // 绝对路径转换为本地文件系统路径 + if (filePath.indexOf('/') === 0) { + return wwwPath + filePath + } + // 相对资源 + if (filePath.indexOf('../') === 0 || filePath.indexOf('./') === 0) { + if (typeof __id__ === 'string') { + return wwwPath + getRealRoute('/' + __id__, filePath) + } else { + const pages = getCurrentPages() + if (pages.length) { + return wwwPath + getRealRoute('/' + pages[pages.length - 1].route, filePath) + } + } + } + return filePath +} + +export function getStatusBarStyle () { + let style = plus.navigator.getStatusBarStyle() + if (style === 'UIStatusBarStyleBlackTranslucent' || style === 'UIStatusBarStyleBlackOpaque' || style === 'null') { + style = 'light' + } else if (style === 'UIStatusBarStyleDefault') { + style = 'dark' + } + return style +} + +const PI = 3.1415926535897932384626 +const a = 6378245.0 +const ee = 0.00669342162296594323 + +export function wgs84togcj02 (lng, lat) { + lat = +lat + lng = +lng + if (outOfChina(lng, lat)) { + return [lng, lat] + } + let dlat = _transformlat(lng - 105.0, lat - 35.0) + let dlng = _transformlng(lng - 105.0, lat - 35.0) + const radlat = lat / 180.0 * PI + let magic = Math.sin(radlat) + magic = 1 - ee * magic * magic + const sqrtmagic = Math.sqrt(magic) + dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI) + dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI) + const mglat = lat + dlat + const mglng = lng + dlng + return [mglng, mglat] +} + +export function gcj02towgs84 (lng, lat) { + lat = +lat + lng = +lng + if (outOfChina(lng, lat)) { + return [lng, lat] + } + let dlat = _transformlat(lng - 105.0, lat - 35.0) + let dlng = _transformlng(lng - 105.0, lat - 35.0) + const radlat = lat / 180.0 * PI + let magic = Math.sin(radlat) + magic = 1 - ee * magic * magic + const sqrtmagic = Math.sqrt(magic) + dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI) + dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI) + const mglat = lat + dlat + const mglng = lng + dlng + return [lng * 2 - mglng, lat * 2 - mglat] +} + +const _transformlat = function (lng, lat) { + let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)) + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0 + ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0 + ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0 + return ret +} +const _transformlng = function (lng, lat) { + let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)) + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0 + ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0 + ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0 + return ret +} + +const outOfChina = function (lng, lat) { + return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false) +} diff --git a/src/platforms/app-plus/service/framework/plus-message.js b/src/platforms/app-plus/service/framework/plus-message.js new file mode 100644 index 000000000..f3a4b0bda --- /dev/null +++ b/src/platforms/app-plus/service/framework/plus-message.js @@ -0,0 +1,36 @@ +import { + publish +} from '../bridge' + +const callbacks = {} +const WEB_INVOKE_APPSERVICE = 'WEB_INVOKE_APPSERVICE' +// 简单处理 view 层与 service 层的通知系统 +/** + * 消费 view 层通知 + */ +export function consumePlusMessage (type, args) { + // 处理 web-view 组件发送的通知 + if (type === WEB_INVOKE_APPSERVICE) { + publish(WEB_INVOKE_APPSERVICE, args.data, args.webviewIds) + return true + } + const callback = callbacks[type] + if (callback) { + callback(args) + if (!callback.keepAlive) { + delete callbacks[type] + } + return true + } + return false +} +/** + * 注册 view 层通知 service 层事件处理 + */ +export function registerPlusMessage (type, callback, keepAlive = true) { + if (callbacks[type]) { + throw new Error(`${type} 已注册:` + (callbacks[type].toString())) + } + callback.keepAlive = !!keepAlive + callbacks[type] = callback +} diff --git a/src/platforms/app-plus/service/framework/safe-area.js b/src/platforms/app-plus/service/framework/safe-area.js new file mode 100644 index 000000000..938f51186 --- /dev/null +++ b/src/platforms/app-plus/service/framework/safe-area.js @@ -0,0 +1,9 @@ +export default { + get bottom () { + if (plus.os.name === 'iOS') { + const safeArea = plus.navigator.getSafeAreaInsets() + return safeArea ? safeArea.bottom : 0 + } + return 0 + } +} diff --git a/src/platforms/app-plus/service/framework/tabbar.js b/src/platforms/app-plus/service/framework/tabbar.js new file mode 100644 index 000000000..05ee8e5e8 --- /dev/null +++ b/src/platforms/app-plus/service/framework/tabbar.js @@ -0,0 +1,497 @@ +import { + TABBAR_HEIGHT +} from '../constants' + +import { + getRealPath, + isTabBarPage +} from '../api/util' + +import safeArea from './safe-area' + +const IMAGE_TOP = 7 +const IMAGE_WIDTH = 24 +const IMAGE_HEIGHT = 24 +const TEXT_TOP = 36 +const TEXT_SIZE = 12 +const TEXT_NOICON_SIZE = 17 +const TEXT_HEIGHT = 12 +const IMAGE_ID = 'TABITEM_IMAGE_' +const TABBAR_VIEW_ID = 'TABBAR_VIEW_' + +let view +let config +let winWidth +let itemWidth +let itemLength +let itemImageLeft +let itemRects = [] +const itemIcons = [] +const itemLayouts = [] +const itemDot = [] +const itemBadge = [] +let itemClickCallback + +let selected = 0 +/** + * tabbar显示状态 + */ +let visible = true + +const init = function () { + const list = config.list + itemLength = config.list.length + + calcItemLayout(list) // 计算选项卡布局 + + initBitmaps(list) // 初始化选项卡图标 + + initView() +} + +let initView = function () { + const viewStyles = { + height: TABBAR_HEIGHT + } + if (config.position === 'top') { + viewStyles.top = 0 + } else { + viewStyles.bottom = 0 + viewStyles.height += safeArea.bottom + } + if (process.env.NODE_ENV !== 'production') { + console.log(`UNIAPP[tabbar]:${JSON.stringify(viewStyles)}`) + } + view = new plus.nativeObj.View(TABBAR_VIEW_ID, viewStyles, getDraws()) + + view.interceptTouchEvent(true) + + view.addEventListener('click', (e) => { + if (!__uniConfig.__ready__) { // 未 ready,不允许点击 + if (process.env.NODE_ENV !== 'production') { + console.log(`UNIAPP[tabbar].prevent`) + } + return + } + const x = e.clientX + const y = e.clientY + for (let i = 0; i < itemRects.length; i++) { + if (isCross(x, y, itemRects[i])) { + const draws = getSelectedDraws(i) + const drawTab = !!draws.length + itemClickCallback && itemClickCallback(config.list[i], i, drawTab) + if (drawTab) { + setTimeout(() => view.draw(draws)) + } + break + } + } + }) + plus.globalEvent.addEventListener('orientationchange', () => { + calcItemLayout(config.list) + if (config.position !== 'top') { + let height = TABBAR_HEIGHT + safeArea.bottom + view.setStyle({ + height: height + }) + if (visible) { + setWebviewPosition(height) + } + } + view.draw(getDraws()) + }) + if (!visible) { + view.hide() + } +} + +let isCross = function (x, y, rect) { + if (x > rect.left && x < (rect.left + rect.width) && y > rect.top && y < (rect.top + rect.height)) { + return true + } + return false +} + +let initBitmaps = function (list) { + for (let i = 0; i < list.length; i++) { + const item = list[i] + if (item.iconData) { + const bitmap = new plus.nativeObj.Bitmap(IMAGE_ID + i) + bitmap.loadBase64Data(item.iconData) + const selectedBitmap = new plus.nativeObj.Bitmap(`${IMAGE_ID}SELECTED_${i}`) + selectedBitmap.loadBase64Data(item.selectedIconData) + itemIcons[i] = { + icon: bitmap, + selectedIcon: selectedBitmap + } + } else if (item.iconPath) { + itemIcons[i] = { + icon: item.iconPath, + selectedIcon: item.selectedIconPath + } + } else { + itemIcons[i] = { + icon: false, + selectedIcon: false + } + } + } +} + +let getDraws = function () { + const backgroundColor = config.backgroundColor + const borderHeight = Math.max(0.5, 1 / plus.screen.scale) // 高分屏最少0.5 + const borderTop = config.position === 'top' ? (TABBAR_HEIGHT - borderHeight) : 0 + const borderStyle = config.borderStyle === 'white' ? 'rgba(255,255,255,0.33)' : 'rgba(0,0,0,0.33)' + + const draws = [{ + id: `${TABBAR_VIEW_ID}BG`, // 背景色 + tag: 'rect', + color: backgroundColor, + position: { + top: 0, + left: 0, + width: '100%', + height: TABBAR_HEIGHT + safeArea.bottom + } + }, { + id: `${TABBAR_VIEW_ID}BORDER`, + tag: 'rect', + color: borderStyle, + position: { + top: borderTop, + left: 0, + width: '100%', + height: `${borderHeight}px` + } + }] + + for (let i = 0; i < itemLength; i++) { + const item = config.list[i] + if (i === selected) { + drawTabItem(draws, i, item.text, config.selectedColor, itemIcons[i].selectedIcon) + } else { + drawTabItem(draws, i, item.text, config.color, itemIcons[i].icon) + } + } + return draws +} + +let getSelectedDraws = function (newSelected) { + if (selected === newSelected) { + return false + } + const draws = [] + const lastSelected = selected + selected = newSelected + drawTabItem(draws, lastSelected) + drawTabItem(draws, selected) + return draws +} +/** + * 获取文字宽度(全角为1) + * @param {*} text + */ +function getFontWidth (text) { + // eslint-disable-next-line + return text.length - (text.match(/[\u0000-\u00ff]/g) || []).length / 2 +} +let calcItemLayout = function () { + winWidth = plus.screen.resolutionWidth + itemWidth = winWidth / itemLength + itemImageLeft = (itemWidth - IMAGE_WIDTH) / 2 + itemRects = [] + let dotTop = 0 + let dotLeft = 0 + for (let i = 0; i < itemLength; i++) { + itemRects.push({ + top: 0, + left: i * itemWidth, + width: itemWidth, + height: TABBAR_HEIGHT + }) + } + + for (let i = 0; i < itemLength; i++) { + const item = config.list[i] + let imgLeft = itemWidth * i + itemImageLeft + if ((item.iconData || item.iconPath) && item.text) { // 图文 + itemLayouts[i] = { + text: { + top: TEXT_TOP, + left: `${i * itemWidth}px`, + width: `${itemWidth}px`, + height: TEXT_HEIGHT + }, + img: { + top: IMAGE_TOP, + left: `${imgLeft}px`, + width: IMAGE_WIDTH, + height: IMAGE_HEIGHT + } + } + dotTop = IMAGE_TOP + dotLeft = imgLeft + IMAGE_WIDTH + } else if (!(item.iconData || item.iconPath) && item.text) { // 仅文字 + let textLeft = i * itemWidth + itemLayouts[i] = { + text: { + top: 0, + left: `${textLeft}px`, + width: `${itemWidth}px`, + height: TABBAR_HEIGHT + } + } + dotTop = (44 - TEXT_NOICON_SIZE) / 2 + dotLeft = textLeft + itemWidth * 0.5 + getFontWidth(item.text) * TEXT_NOICON_SIZE * 0.5 + } else if ((item.iconData || item.iconPath) && !item.text) { // 仅图片 + const diff = 10 + let imgTop = (TABBAR_HEIGHT - IMAGE_HEIGHT - diff) / 2 + let imgLeft = itemWidth * i + itemImageLeft - diff / 2 + itemLayouts[i] = { + img: { + top: `${imgTop}px`, + left: `${imgLeft}px`, + width: IMAGE_WIDTH + diff, + height: IMAGE_HEIGHT + diff + } + } + dotTop = imgTop + dotLeft = imgLeft + IMAGE_WIDTH + diff + } + let height = itemBadge[i] ? 14 : 10 + let badge = itemBadge[i] || '0' + let font = getFontWidth(badge) - 0.5 + font = font > 1 ? 1 : font + let width = height + font * 12 + width = width < height ? height : width + let itemLayout = itemLayouts[i] + if (itemLayout) { + itemLayout.doc = { + top: dotTop, + left: `${dotLeft - width * 0.382}px`, + width: `${width}px`, + height: `${height}px` + } + itemLayout.badge = { + top: dotTop, + left: `${dotLeft - width * 0.382}px`, + width: `${width}px`, + height: `${height}px` + } + } + } +} + +let drawTabItem = function (draws, index) { + const layout = itemLayouts[index] + + const item = config.list[index] + + let color = config.color + let icon = itemIcons[index].icon + let dot = itemDot[index] + let badge = itemBadge[index] || '' + + if (index === selected) { + color = config.selectedColor + icon = itemIcons[index].selectedIcon + } + + if (icon) { + draws.push({ + id: `${TABBAR_VIEW_ID}ITEM_${index}_ICON`, + tag: 'img', + position: layout.img, + src: icon + }) + } + if (item.text) { + draws.push({ + id: `${TABBAR_VIEW_ID}ITEM_${index}_TEXT`, + tag: 'font', + position: layout.text, + text: item.text, + textStyles: { + size: icon ? TEXT_SIZE : `${TEXT_NOICON_SIZE}px`, + color + } + }) + } + const hiddenPosition = { + letf: 0, + top: 0, + width: 0, + height: 0 + } + // 绘制小红点(角标背景) + draws.push({ + id: `${TABBAR_VIEW_ID}ITEM_${index}_DOT`, + tag: 'rect', + position: (dot || badge) ? layout.doc : hiddenPosition, + rectStyles: { + color: '#ff0000', + radius: badge ? '7px' : '5px' + } + }) + // 绘制角标文本 + draws.push({ + id: `${TABBAR_VIEW_ID}ITEM_${index}_BADGE`, + tag: 'font', + position: badge ? layout.badge : hiddenPosition, + text: badge || ' ', + textStyles: { + align: 'center', + verticalAlign: 'middle', + color: badge ? '#ffffff' : 'rgba(0,0,0,0)', + overflow: 'ellipsis', + size: '10px' + } + }) +} +/** + * { + "color": "#7A7E83", + "selectedColor": "#3cc51f", + "borderStyle": "black", + "backgroundColor": "#ffffff", + "list": [{ + "pagePath": "page/component/index.html", + "iconData": "", + "selectedIconData": "", + "text": "组件" + }, { + "pagePath": "page/API/index.html", + "iconData": "", + "selectedIconData": "", + "text": "接口" + }], + "position":"bottom"//bottom|top + } + */ + +/** + * 设置角标 + * @param {string} type + * @param {number} index + * @param {string} text + */ +function setTabBarBadge (type, index, text) { + if (type === 'none') { + itemDot[index] = false + itemBadge[index] = '' + } else if (type === 'text') { + itemBadge[index] = text + } else if (type === 'redDot') { + itemDot[index] = true + } + if (view) { + calcItemLayout(config.list) + view.draw(getDraws()) + } +} +/** + * 动态设置 tabBar 某一项的内容 + */ +function setTabBarItem (index, text, iconPath, selectedIconPath) { + if (iconPath || selectedIconPath) { + let itemIcon = itemIcons[index] = itemIcons[index] || {} + if (iconPath) { + itemIcon.icon = getRealPath(iconPath) + } + if (selectedIconPath) { + itemIcon.selectedIcon = getRealPath(selectedIconPath) + } + } + if (text) { + config.list[index].text = text + } + view.draw(getDraws()) +} +/** + * 动态设置 tabBar 的整体样式 + * @param {Object} style 样式 + */ +function setTabBarStyle (style) { + for (const key in style) { + config[key] = style[key] + } + view.draw(getDraws()) +} +/** + * 设置tab页底部或顶部距离 + * @param {*} value 距离 + */ +function setWebviewPosition (value) { + const position = config.position === 'top' ? 'top' : 'bottom' + plus.webview.all().forEach(webview => { + if (isTabBarPage(String(webview.__uniapp_route))) { + webview.setStyle({ + [position]: value + }) + } + }) +} +/** + * 隐藏 tabBar + * @param {boolean} animation 是否需要动画效果 暂未支持 + */ +function hideTabBar (animation) { + if (visible === false) { + return + } + visible = false + if (view) { + view.hide() + setWebviewPosition(0) + } +} +/** + * 显示 tabBar + * @param {boolean} animation 是否需要动画效果 暂未支持 + */ +function showTabBar (animation) { + if (visible === true) { + return + } + visible = true + if (view) { + view.show() + setWebviewPosition(TABBAR_HEIGHT + safeArea.bottom) + } +} + +export default { + init (options, clickCallback) { + if (options && options.list.length) { + selected = options.selected || 0 + config = options + config.position = 'bottom' // 暂时强制使用bottom + itemClickCallback = clickCallback + init() + return view + } + }, + switchTab (page) { + if (itemLength) { + for (let i = 0; i < itemLength; i++) { + if (config.list[i].pagePath === (`${page}.html`)) { + const draws = getSelectedDraws(i) + if (draws.length) { + view.draw(draws) + } + return true + } + } + } + return false + }, + setTabBarBadge, + setTabBarItem, + setTabBarStyle, + hideTabBar, + showTabBar, + get visible () { + return visible + } +} -- GitLab