浏览代码

[Android] Introduce IncomingCallView

It's a separate view (on the native side) and app (on the JavaScript side) so
applications can use it independently.

Co-authored-by: Shuai Li <sli@atlassian.com>
Co-authored-by: Pawel Domas <pawel.domas@jitsi.org>
j8
Saúl Ibarra Corretgé 6 年前
父节点
当前提交
ea22d12581

+ 1
- 0
android/sdk/build.gradle 查看文件

@@ -28,6 +28,7 @@ dependencies {
28 28
     compile project(':react-native-fetch-blob')
29 29
     compile project(':react-native-immersive')
30 30
     compile project(':react-native-keep-awake')
31
+    compile project(':react-native-linear-gradient')
31 32
     compile project(':react-native-locale-detector')
32 33
     compile project(':react-native-sound')
33 34
     compile project(':react-native-vector-icons')

+ 1
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java 查看文件

@@ -119,6 +119,7 @@ public class ReactInstanceManagerHolder {
119 119
                 .setApplication(application)
120 120
                 .setBundleAssetName("index.android.bundle")
121 121
                 .setJSMainModulePath("index.android")
122
+                .addPackage(new com.BV.LinearGradient.LinearGradientPackage())
122 123
                 .addPackage(new com.calendarevents.CalendarEventsPackage())
123 124
                 .addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
124 125
                 .addPackage(new com.facebook.react.shell.MainReactPackage())

+ 69
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/incoming_call/IncomingCallInfo.java 查看文件

@@ -0,0 +1,69 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk.incoming_call;
18
+
19
+import android.support.annotation.NonNull;
20
+
21
+public class IncomingCallInfo {
22
+    /**
23
+     * URL for the caller avatar.
24
+     */
25
+    private final String callerAvatarUrl;
26
+
27
+    /**
28
+     * Caller's name.
29
+     */
30
+    private final String callerName;
31
+
32
+    /**
33
+     * Whether this is a regular call or a video call.
34
+     */
35
+    private final boolean hasVideo;
36
+
37
+    public IncomingCallInfo(
38
+            @NonNull String callerName,
39
+            @NonNull String callerAvatarUrl,
40
+            boolean hasVideo) {
41
+        this.callerName = callerName;
42
+        this.callerAvatarUrl = callerAvatarUrl;
43
+        this.hasVideo = hasVideo;
44
+    }
45
+
46
+    /**
47
+     * Gets the caller's avatar URL.
48
+     * @return - The URL as a string.
49
+     */
50
+    public String getCallerAvatarUrl() {
51
+        return callerAvatarUrl;
52
+    }
53
+
54
+    /**
55
+     * Gets the caller's name.
56
+     * @return - The caller's name.
57
+     */
58
+    public String getCallerName() {
59
+        return callerName;
60
+    }
61
+
62
+    /**
63
+     * Gets whether the call is a video call or not.
64
+     * @return - True if this call has video, false otherwise.
65
+     */
66
+    public boolean hasVideo() {
67
+        return hasVideo;
68
+    }
69
+}

+ 104
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/incoming_call/IncomingCallView.java 查看文件

@@ -0,0 +1,104 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk.incoming_call;
18
+
19
+import android.content.Context;
20
+import android.os.Bundle;
21
+import android.support.annotation.NonNull;
22
+
23
+import com.facebook.react.bridge.ReadableMap;
24
+
25
+import org.jitsi.meet.sdk.BaseReactView;
26
+import org.jitsi.meet.sdk.ListenerUtils;
27
+
28
+import java.lang.reflect.Method;
29
+import java.util.Map;
30
+
31
+
32
+public class IncomingCallView extends BaseReactView {
33
+    /**
34
+     * The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
35
+     * redux action types.
36
+     */
37
+    private static final Map<String, Method> LISTENER_METHODS
38
+        = ListenerUtils.slurpListenerMethods(IncomingCallViewListener.class);
39
+
40
+    /**
41
+     * {@link IncomingCallViewListener} instance for reporting events occurring
42
+     * in Jitsi Meet.
43
+     */
44
+    private IncomingCallViewListener listener;
45
+
46
+    public IncomingCallView(@NonNull Context context) {
47
+        super(context);
48
+    }
49
+
50
+    /**
51
+     * Handler for {@link ExternalAPIModule} events.
52
+     *
53
+     * @param name - Name of the event.
54
+     * @param data - Event data.
55
+     */
56
+    @Override
57
+    public void onExternalAPIEvent(String name, ReadableMap data) {
58
+        IncomingCallViewListener listener = getListener();
59
+        if (listener != null) {
60
+            ListenerUtils.runListenerMethod(
61
+                listener, LISTENER_METHODS, name, data);
62
+        }
63
+    }
64
+
65
+    /**
66
+     * Gets the {@link IncomingCallViewListener} set on this
67
+     * {@code IncomingCallView}.
68
+     *
69
+     * @return The {@code IncomingCallViewListener} set on this
70
+     * {@code IncomingCallView}.
71
+     */
72
+    public IncomingCallViewListener getListener() {
73
+        return listener;
74
+    }
75
+
76
+    /**
77
+     * Sets the information for the incoming call this {@code IncomingCallView}
78
+     * represents.
79
+     *
80
+     * @param callInfo - {@link IncomingCallInfo} object representing the caller
81
+     *                 information.
82
+     */
83
+    public void setIncomingCallInfo(IncomingCallInfo callInfo) {
84
+        Bundle props = new Bundle();
85
+
86
+        props.putString("callerAvatarUrl", callInfo.getCallerAvatarUrl());
87
+        props.putString("callerName", callInfo.getCallerName());
88
+        props.putBoolean("hasVideo", callInfo.hasVideo());
89
+
90
+        createReactRootView("IncomingCallApp", props);
91
+    }
92
+
93
+    /**
94
+     * Sets a specific {@link IncomingCallViewListener} on this
95
+     * {@code IncomingCallView}.
96
+     *
97
+     * @param listener The {@code IncomingCallViewListener} to set on this
98
+     * {@code IncomingCallView}.
99
+     */
100
+    public void setListener(IncomingCallViewListener listener) {
101
+        this.listener = listener;
102
+    }
103
+
104
+}

+ 41
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/incoming_call/IncomingCallViewListener.java 查看文件

@@ -0,0 +1,41 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk.incoming_call;
18
+
19
+import java.util.Map;
20
+
21
+/**
22
+ * Interface for listening to events coming from Jitsi Meet, related to
23
+ * {@link IncomingCallView};
24
+ */
25
+public interface IncomingCallViewListener {
26
+    /**
27
+     * Called when the user presses the "answer" button on the
28
+     * {@link IncomingCallView}.
29
+     *
30
+     * @param data - Unused at the moment.
31
+     */
32
+    void onIncomingCallAnswered(Map<String, Object> data);
33
+
34
+    /**
35
+     * Called when the user presses the "decline" button on the
36
+     * {@link IncomingCallView}.
37
+     *
38
+     * @param data - Unused at the moment.
39
+     */
40
+    void onIncomingCallDeclined(Map<String, Object> data);
41
+}

+ 2
- 0
android/settings.gradle 查看文件

@@ -9,6 +9,8 @@ include ':react-native-immersive'
9 9
 project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
10 10
 include ':react-native-keep-awake'
11 11
 project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
12
+include ':react-native-linear-gradient'
13
+project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
12 14
 include ':react-native-locale-detector'
13 15
 project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
14 16
 include ':react-native-sound'

+ 7
- 0
lang/main.json 查看文件

@@ -643,5 +643,12 @@
643 643
         "rejected": "Rejected",
644 644
         "ignored": "Ignored",
645 645
         "expired": "Expired"
646
+    },
647
+    "incomingCall": {
648
+        "answer": "Answer",
649
+        "audioCallTitle": "Incoming call",
650
+        "decline": "Dismiss",
651
+        "productLabel": "from Jitsi Meet",
652
+        "videoCallTitle": "Incoming video call"
646 653
     }
647 654
 }

+ 8
- 0
package-lock.json 查看文件

@@ -12751,6 +12751,14 @@
12751 12751
       "resolved": "https://registry.npmjs.org/react-native-keep-awake/-/react-native-keep-awake-2.0.6.tgz",
12752 12752
       "integrity": "sha512-ketZKC6G49W4iblKYCnIA5Tcx78Yu48n/K5XzZUnMm69wAnZxs1054Re2V5xpSwX5VZasOBjW1iI1cTjtB/H5g=="
12753 12753
     },
12754
+    "react-native-linear-gradient": {
12755
+      "version": "2.4.0",
12756
+      "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.4.0.tgz",
12757
+      "integrity": "sha512-h4nwmcjfeedSiHGBmQkMmCSIqm3196YtT1AtbAqE93jgAcpib0btvoCx8nBUemmhfm+CA5mFEh8p5biA4wFw/A==",
12758
+      "requires": {
12759
+        "prop-types": "^15.5.10"
12760
+      }
12761
+    },
12754 12762
     "react-native-locale-detector": {
12755 12763
       "version": "github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9",
12756 12764
       "from": "react-native-locale-detector@github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9"

+ 1
- 0
package.json 查看文件

@@ -63,6 +63,7 @@
63 63
     "react-native-img-cache": "1.5.2",
64 64
     "react-native-immersive": "1.1.0",
65 65
     "react-native-keep-awake": "2.0.6",
66
+    "react-native-linear-gradient": "2.4.0",
66 67
     "react-native-locale-detector": "github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9",
67 68
     "react-native-prompt": "1.0.0",
68 69
     "react-native-sound": "0.10.9",

+ 27
- 0
react/features/mobile/incoming-call/actionTypes.js 查看文件

@@ -0,0 +1,27 @@
1
+/**
2
+ * The type of redux action to answer an incoming call.
3
+ *
4
+ * {
5
+ *     type: INCOMING_CALL_ANSWERED,
6
+ * }
7
+ */
8
+export const INCOMING_CALL_ANSWERED = Symbol('INCOMING_CALL_ANSWERED');
9
+
10
+/**
11
+ * The type of redux action to decline an incoming call.
12
+ *
13
+ * {
14
+ *     type: INCOMING_CALL_DECLINED,
15
+ * }
16
+ */
17
+export const INCOMING_CALL_DECLINED = Symbol('INCOMING_CALL_DECLINED');
18
+
19
+/**
20
+ * The type of redux action to receive an incoming call.
21
+ *
22
+ * {
23
+ *     type: INCOMING_CALL_RECEIVED,
24
+ *     caller: Object
25
+ * }
26
+ */
27
+export const INCOMING_CALL_RECEIVED = Symbol('INCOMING_CALL_RECEIVED');

+ 49
- 0
react/features/mobile/incoming-call/actions.js 查看文件

@@ -0,0 +1,49 @@
1
+// @flow
2
+
3
+import {
4
+    INCOMING_CALL_ANSWERED,
5
+    INCOMING_CALL_DECLINED,
6
+    INCOMING_CALL_RECEIVED
7
+} from './actionTypes';
8
+
9
+/**
10
+ * Answers a received incoming call.
11
+ *
12
+ * @returns {{
13
+ *     type: INCOMING_CALL_ANSWERED
14
+ * }}
15
+ */
16
+export function incomingCallAnswered() {
17
+    return {
18
+        type: INCOMING_CALL_ANSWERED
19
+    };
20
+}
21
+
22
+/**
23
+ * Declines a received incoming call.
24
+ *
25
+ * @returns {{
26
+ *     type: INCOMING_CALL_DECLINED
27
+ * }}
28
+ */
29
+export function incomingCallDeclined() {
30
+    return {
31
+        type: INCOMING_CALL_DECLINED
32
+    };
33
+}
34
+
35
+/**
36
+ * Shows a received incoming call.
37
+ *
38
+ * @param {Object} caller - The caller of an incoming call.
39
+ * @returns {{
40
+ *     type: INCOMING_CALL_RECEIVED,
41
+ *     caller: Object
42
+ * }}
43
+ */
44
+export function incomingCallReceived(caller: Object) {
45
+    return {
46
+        type: INCOMING_CALL_RECEIVED,
47
+        caller
48
+    };
49
+}

+ 39
- 0
react/features/mobile/incoming-call/components/AnswerButton.js 查看文件

@@ -0,0 +1,39 @@
1
+// @flow
2
+
3
+import { connect } from 'react-redux';
4
+
5
+import { AbstractButton } from '../../../base/toolbox';
6
+import { translate } from '../../../base/i18n';
7
+import type { AbstractButtonProps } from '../../../base/toolbox';
8
+
9
+import { incomingCallAnswered } from '../actions';
10
+
11
+type Props = AbstractButtonProps & {
12
+
13
+    /**
14
+     * The redux {@code dispatch} function.
15
+     */
16
+    dispatch: Function
17
+};
18
+
19
+/**
20
+ * An implementation of a button which accepts an incoming call.
21
+ */
22
+class AnswerButton extends AbstractButton<Props, *> {
23
+    accessibilityLabel = 'incomingCall.answer';
24
+    iconName = 'hangup';
25
+    label = 'incomingCall.answer';
26
+
27
+    /**
28
+     * Handles clicking / pressing the button, and answers the incoming call.
29
+     *
30
+     * @protected
31
+     * @returns {void}
32
+     */
33
+    _handleClick() {
34
+        this.props.dispatch(incomingCallAnswered());
35
+    }
36
+
37
+}
38
+
39
+export default translate(connect()(AnswerButton));

+ 39
- 0
react/features/mobile/incoming-call/components/DeclineButton.js 查看文件

@@ -0,0 +1,39 @@
1
+// @flow
2
+
3
+import { connect } from 'react-redux';
4
+
5
+import { AbstractButton } from '../../../base/toolbox';
6
+import { translate } from '../../../base/i18n';
7
+import type { AbstractButtonProps } from '../../../base/toolbox';
8
+
9
+import { incomingCallDeclined } from '../actions';
10
+
11
+type Props = AbstractButtonProps & {
12
+
13
+    /**
14
+     * The redux {@code dispatch} function.
15
+     */
16
+    dispatch: Function
17
+};
18
+
19
+/**
20
+ * An implementation of a button which rejects an incoming call.
21
+ */
22
+class DeclineButton extends AbstractButton<Props, *> {
23
+    accessibilityLabel = 'incomingCall.decline';
24
+    iconName = 'hangup';
25
+    label = 'incomingCall.decline';
26
+
27
+    /**
28
+     * Handles clicking / pressing the button, and declines the incoming call.
29
+     *
30
+     * @protected
31
+     * @returns {void}
32
+     */
33
+    _handleClick() {
34
+        this.props.dispatch(incomingCallDeclined());
35
+    }
36
+
37
+}
38
+
39
+export default translate(connect()(DeclineButton));

+ 63
- 0
react/features/mobile/incoming-call/components/IncomingCallApp.js 查看文件

@@ -0,0 +1,63 @@
1
+// @flow
2
+
3
+import { BaseApp } from '../../../base/app';
4
+
5
+import { incomingCallReceived } from '../actions';
6
+
7
+import IncomingCallPage from './IncomingCallPage';
8
+
9
+type Props = {
10
+
11
+    /**
12
+     * URL of the avatar for the caller.
13
+     */
14
+    callerAvatarUrl: string,
15
+
16
+    /**
17
+     * Name of the caller.
18
+     */
19
+    callerName: string,
20
+
21
+    /**
22
+     * Whether this is a video call or not.
23
+     */
24
+    hasVideo: boolean
25
+};
26
+
27
+/**
28
+ * Root application component for incoming call.
29
+ *
30
+ * @extends BaseApp
31
+ */
32
+export default class IncomingCallApp extends BaseApp<Props> {
33
+    _init: Promise<*>;
34
+
35
+    /**
36
+     * Navigates to {@link IncomingCallPage} upon mount.
37
+     *
38
+     * NOTE: This was implmented here instead of in a middleware for
39
+     * the APP_WILL_MOUNT action because that would run also for {@link App}.
40
+     *
41
+     * @returns {void}
42
+     */
43
+    componentWillMount() {
44
+        super.componentWillMount();
45
+
46
+        this._init.then(() => {
47
+            const { dispatch } = this.state.store;
48
+            const {
49
+                callerAvatarUrl: avatarUrl,
50
+                callerName: name,
51
+                hasVideo
52
+            } = this.props;
53
+
54
+            dispatch(incomingCallReceived({
55
+                avatarUrl,
56
+                name,
57
+                hasVideo
58
+            }));
59
+
60
+            super._navigate({ component: IncomingCallPage });
61
+        });
62
+    }
63
+}

+ 182
- 0
react/features/mobile/incoming-call/components/IncomingCallPage.js 查看文件

@@ -0,0 +1,182 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { Image, Text, View } from 'react-native';
5
+import LinearGradient from 'react-native-linear-gradient';
6
+import { connect } from 'react-redux';
7
+
8
+import { translate } from '../../../base/i18n';
9
+import { Avatar } from '../../../base/participants';
10
+
11
+import AnswerButton from './AnswerButton';
12
+import DeclineButton from './DeclineButton';
13
+import styles, {
14
+    AVATAR_BORDER_GRADIENT,
15
+    BACKGROUND_OVERLAY_GRADIENT,
16
+    CALLER_AVATAR_SIZE
17
+} from './styles';
18
+
19
+/**
20
+ * The type of React {@code Component} props of {@link IncomingCallPage}.
21
+ */
22
+type Props = {
23
+
24
+    /**
25
+     * Caller's avatar URL.
26
+     */
27
+    _callerAvatarUrl: string,
28
+
29
+    /**
30
+     * Caller's name.
31
+     */
32
+    _callerName: string,
33
+
34
+    /**
35
+     * Whether the call has video or not.
36
+     */
37
+    _hasVideo: boolean,
38
+
39
+    /**
40
+     * Helper for translating strings.
41
+     */
42
+    t: Function
43
+};
44
+
45
+/**
46
+ * The React {@code Component} displays an incoming call screen.
47
+ */
48
+class IncomingCallPage extends Component<Props> {
49
+    /**
50
+     * Implements React's {@link Component#render()}.
51
+     *
52
+     * @inheritdoc
53
+     * @returns {ReactElement}
54
+     */
55
+    render() {
56
+        const { t, _callerName, _hasVideo } = this.props;
57
+        const callTitle
58
+            = _hasVideo
59
+                ? t('incomingCall.videoCallTitle')
60
+                : t('incomingCall.audioCallTitle');
61
+
62
+        return (
63
+            <View style = { styles.pageContainer }>
64
+                <View style = { styles.backgroundAvatar }>
65
+                    <Image
66
+                        source = {{ uri: this.props._callerAvatarUrl }}
67
+                        style = { styles.backgroundAvatarImage } />
68
+                </View>
69
+                <LinearGradient
70
+                    colors = { BACKGROUND_OVERLAY_GRADIENT }
71
+                    style = { styles.backgroundOverlayGradient } />
72
+                <Text style = { styles.title }>
73
+                    { callTitle }
74
+                </Text>
75
+                <Text
76
+                    numberOfLines = { 6 }
77
+                    style = { styles.callerName } >
78
+                    { _callerName }
79
+                </Text>
80
+                <Text style = { styles.productLabel }>
81
+                    { t('incomingCall.productLabel') }
82
+                </Text>
83
+                { this._renderCallerAvatar() }
84
+                { this._renderButtons() }
85
+            </View>
86
+        );
87
+    }
88
+
89
+    /**
90
+     * Renders caller avatar.
91
+     *
92
+     * @private
93
+     * @returns {React$Node}
94
+     */
95
+    _renderCallerAvatar() {
96
+        return (
97
+            <View style = { styles.avatarContainer }>
98
+                <LinearGradient
99
+                    colors = { AVATAR_BORDER_GRADIENT }
100
+                    style = { styles.avatarBorder } />
101
+                <View style = { styles.avatar }>
102
+                    <Avatar
103
+                        size = { CALLER_AVATAR_SIZE }
104
+                        uri = { this.props._callerAvatarUrl } />
105
+                </View>
106
+            </View>
107
+        );
108
+    }
109
+
110
+    /**
111
+     * Renders buttons.
112
+     *
113
+     * @private
114
+     * @returns {React$Node}
115
+     */
116
+    _renderButtons() {
117
+        const { t } = this.props;
118
+
119
+        return (
120
+            <View style = { styles.buttonsContainer }>
121
+                <View style = { styles.buttonWrapper } >
122
+                    <DeclineButton
123
+                        styles = { styles.declineButtonStyles } />
124
+                    <Text style = { styles.buttonText }>
125
+                        { t('incomingCall.decline') }
126
+                    </Text>
127
+                </View>
128
+                <View style = { styles.buttonWrapper }>
129
+                    <AnswerButton
130
+                        styles = { styles.answerButtonStyles } />
131
+                    <Text style = { styles.buttonText }>
132
+                        { t('incomingCall.answer') }
133
+                    </Text>
134
+                </View>
135
+            </View>
136
+        );
137
+    }
138
+}
139
+
140
+/**
141
+ * Maps (parts of) the redux state to the component's props.
142
+ *
143
+ * @param {Object} state - The redux state.
144
+ * @param {Object} ownProps - The component's own props.
145
+ * @private
146
+ * @returns {{
147
+ *     _callerName: string,
148
+ *     _callerAvatarUrl: string,
149
+ *     _hasVideo: boolean
150
+ * }}
151
+ */
152
+function _mapStateToProps(state) {
153
+    const { caller } = state['features/mobile/incoming-call'] || {};
154
+
155
+    return {
156
+        /**
157
+         * The caller's avatar url.
158
+         *
159
+         * @private
160
+         * @type {string}
161
+         */
162
+        _callerAvatarUrl: caller.avatarUrl,
163
+
164
+        /**
165
+         * The caller's name.
166
+         *
167
+         * @private
168
+         * @type {string}
169
+         */
170
+        _callerName: caller.name,
171
+
172
+        /**
173
+         * Whether the call has video or not.
174
+         *
175
+         * @private
176
+         * @type {boolean}
177
+         */
178
+        _hasVideo: caller.hasVideo
179
+    };
180
+}
181
+
182
+export default translate(connect(_mapStateToProps)(IncomingCallPage));

+ 2
- 0
react/features/mobile/incoming-call/components/index.js 查看文件

@@ -0,0 +1,2 @@
1
+export { default as IncomingCallApp } from './IncomingCallApp';
2
+export { default as IncomingCallPage } from './IncomingCallPage';

+ 149
- 0
react/features/mobile/incoming-call/components/styles.js 查看文件

@@ -0,0 +1,149 @@
1
+import {
2
+    ColorPalette,
3
+    createStyleSheet
4
+} from '../../../base/styles';
5
+
6
+export const AVATAR_BORDER_GRADIENT = [ '#4C9AFF', '#0052CC' ];
7
+
8
+export const BACKGROUND_OVERLAY_GRADIENT = [ '#0052CC', '#4C9AFF' ];
9
+
10
+const BUTTON_SIZE = 56;
11
+
12
+export const CALLER_AVATAR_SIZE = 128;
13
+
14
+const CALLER_AVATAR_BORDER_WIDTH = 3;
15
+
16
+const CALLER_AVATAR_CIRCLE_SIZE
17
+    = CALLER_AVATAR_SIZE + (2 * CALLER_AVATAR_BORDER_WIDTH);
18
+
19
+const PAGE_PADDING = 48;
20
+
21
+const LINE_SPACING = 8;
22
+
23
+const _icon = {
24
+    alignSelf: 'center',
25
+    color: ColorPalette.white,
26
+    fontSize: 32
27
+};
28
+
29
+const _responseButton = {
30
+    alignSelf: 'center',
31
+    borderRadius: BUTTON_SIZE / 2,
32
+    borderWidth: 0,
33
+    flex: 0,
34
+    flexDirection: 'row',
35
+    height: BUTTON_SIZE,
36
+    justifyContent: 'center',
37
+    width: BUTTON_SIZE
38
+};
39
+
40
+const _text = {
41
+    color: ColorPalette.white,
42
+    fontSize: 16
43
+};
44
+
45
+export default createStyleSheet({
46
+    answerButtonStyles: {
47
+        iconStyle: {
48
+            ..._icon,
49
+            transform: [
50
+                { rotateZ: '130deg' }
51
+            ]
52
+        },
53
+        style: {
54
+            ..._responseButton,
55
+            backgroundColor: ColorPalette.green
56
+        },
57
+        underlayColor: ColorPalette.buttonUnderlay
58
+    },
59
+
60
+    avatar: {
61
+        position: 'absolute',
62
+        marginLeft: CALLER_AVATAR_BORDER_WIDTH,
63
+        marginTop: CALLER_AVATAR_BORDER_WIDTH
64
+    },
65
+
66
+    avatarBorder: {
67
+        borderRadius: CALLER_AVATAR_CIRCLE_SIZE / 2,
68
+        height: CALLER_AVATAR_CIRCLE_SIZE,
69
+        position: 'absolute',
70
+        width: CALLER_AVATAR_CIRCLE_SIZE
71
+    },
72
+
73
+    avatarContainer: {
74
+        height: CALLER_AVATAR_CIRCLE_SIZE,
75
+        marginTop: LINE_SPACING * 4,
76
+        width: CALLER_AVATAR_CIRCLE_SIZE
77
+    },
78
+
79
+    backgroundAvatar: {
80
+        bottom: 0,
81
+        left: 0,
82
+        position: 'absolute',
83
+        right: 0,
84
+        top: 0
85
+    },
86
+
87
+    backgroundAvatarImage: {
88
+        flex: 1
89
+    },
90
+
91
+    backgroundOverlayGradient: {
92
+        bottom: 0,
93
+        left: 0,
94
+        opacity: 0.9,
95
+        position: 'absolute',
96
+        right: 0,
97
+        top: 0
98
+    },
99
+
100
+    buttonsContainer: {
101
+        alignItems: 'flex-end',
102
+        flex: 1,
103
+        flexDirection: 'row'
104
+    },
105
+
106
+    buttonText: {
107
+        ..._text,
108
+        alignSelf: 'center',
109
+        marginTop: 1.5 * LINE_SPACING
110
+    },
111
+
112
+    buttonWrapper: {
113
+        flex: 1
114
+    },
115
+
116
+    callerName: {
117
+        ..._text,
118
+        fontSize: 36,
119
+        marginBottom: LINE_SPACING,
120
+        marginLeft: PAGE_PADDING,
121
+        marginRight: PAGE_PADDING,
122
+        marginTop: LINE_SPACING,
123
+        textAlign: 'center'
124
+    },
125
+
126
+    declineButtonStyles: {
127
+        iconStyle: _icon,
128
+        style: {
129
+            ..._responseButton,
130
+            backgroundColor: ColorPalette.red
131
+        },
132
+        underlayColor: ColorPalette.buttonUnderlay
133
+    },
134
+
135
+    pageContainer: {
136
+        alignItems: 'center',
137
+        flex: 1,
138
+        paddingBottom: PAGE_PADDING,
139
+        paddingTop: PAGE_PADDING
140
+    },
141
+
142
+    productLabel: {
143
+        ..._text
144
+    },
145
+
146
+    title: {
147
+        ..._text
148
+    }
149
+});

+ 4
- 0
react/features/mobile/incoming-call/index.js 查看文件

@@ -0,0 +1,4 @@
1
+export * from './components';
2
+
3
+import './middleware';
4
+import './reducer';

+ 31
- 0
react/features/mobile/incoming-call/middleware.js 查看文件

@@ -0,0 +1,31 @@
1
+// @flow
2
+
3
+import { MiddlewareRegistry } from '../../base/redux';
4
+import { getSymbolDescription } from '../../base/util';
5
+
6
+import { sendEvent } from '../external-api';
7
+
8
+import {
9
+    INCOMING_CALL_ANSWERED,
10
+    INCOMING_CALL_DECLINED
11
+} from './actionTypes';
12
+
13
+/**
14
+ * Middleware that captures Redux actions and uses the IncomingCallExternalAPI
15
+ * module to turn them into native events so the application knows about them.
16
+ *
17
+ * @param {Store} store - The redux store.
18
+ * @returns {Function}
19
+ */
20
+MiddlewareRegistry.register(store => next => action => {
21
+    const result = next(action);
22
+
23
+    switch (action.type) {
24
+    case INCOMING_CALL_ANSWERED:
25
+    case INCOMING_CALL_DECLINED:
26
+        sendEvent(store, getSymbolDescription(action.type), /* data */ {});
27
+        break;
28
+    }
29
+
30
+    return result;
31
+});

+ 26
- 0
react/features/mobile/incoming-call/reducer.js 查看文件

@@ -0,0 +1,26 @@
1
+/* @flow */
2
+
3
+import { assign, ReducerRegistry } from '../../base/redux';
4
+
5
+import {
6
+    INCOMING_CALL_ANSWERED,
7
+    INCOMING_CALL_DECLINED,
8
+    INCOMING_CALL_RECEIVED
9
+} from './actionTypes';
10
+
11
+ReducerRegistry.register(
12
+    'features/mobile/incoming-call', (state = {}, action) => {
13
+        switch (action.type) {
14
+        case INCOMING_CALL_ANSWERED:
15
+        case INCOMING_CALL_DECLINED:
16
+            return assign(state, {
17
+                caller: undefined
18
+            });
19
+        case INCOMING_CALL_RECEIVED:
20
+            return assign(state, {
21
+                caller: action.caller
22
+            });
23
+        }
24
+
25
+        return state;
26
+    });

+ 24
- 37
react/index.native.js 查看文件

@@ -1,3 +1,5 @@
1
+// @flow
2
+
1 3
 // FIXME The bundler-related (and the browser-related) polyfills were born at
2 4
 // the very early days of prototyping the execution of lib-jitsi-meet on
3 5
 // react-native. Today, the feature base/lib-jitsi-meet should not be
@@ -12,12 +14,28 @@ import './features/base/lib-jitsi-meet/native/polyfills-bundler';
12 14
 // PropTypes from 'prop-types' instead of 'react'.
13 15
 import './features/base/react/prop-types-polyfill';
14 16
 
15
-import PropTypes from 'prop-types';
16 17
 import React, { Component } from 'react';
17 18
 import { AppRegistry, Linking, NativeModules } from 'react-native';
18 19
 
19 20
 import { App } from './features/app';
20 21
 import { equals } from './features/base/redux';
22
+import { IncomingCallApp } from './features/mobile/incoming-call';
23
+
24
+type Props = {
25
+
26
+    /**
27
+     * The URL, if any, with which the app was launched.
28
+     */
29
+    url: Object | string
30
+};
31
+
32
+type State = {
33
+
34
+    /**
35
+     * The URL, if any, with which the app was launched.
36
+     */
37
+    url: ?Object | string
38
+};
21 39
 
22 40
 /**
23 41
  * React Native doesn't support specifying props to the main/root component (in
@@ -26,51 +44,17 @@ import { equals } from './features/base/redux';
26 44
  *
27 45
  * @extends Component
28 46
  */
29
-class Root extends Component {
30
-    /**
31
-     * {@code Root} component's property types.
32
-     *
33
-     * @static
34
-     */
35
-    static propTypes = {
36
-        /**
37
-         * The URL, if any, with which the app was launched.
38
-         */
39
-        url: PropTypes.oneOfType([
40
-            PropTypes.object,
41
-            PropTypes.string
42
-        ]),
43
-
44
-        /**
45
-         * Whether the Welcome page is enabled. If {@code true}, the Welcome
46
-         * page is rendered when the {@link App} is not at a location (URL)
47
-         * identifying a Jitsi Meet conference/room.
48
-         */
49
-        welcomePageEnabled: PropTypes.bool
50
-    };
51
-
47
+class Root extends Component<Props, State> {
52 48
     /**
53 49
      * Initializes a new {@code Root} instance.
54 50
      *
55
-     * @param {Object} props - The read-only properties with which the new
51
+     * @param {Props} props - The read-only properties with which the new
56 52
      * instance is to be initialized.
57 53
      */
58 54
     constructor(props) {
59 55
         super(props);
60 56
 
61
-        /**
62
-         * The initial state of this Component.
63
-         *
64
-         * @type {{
65
-         *     url: object|string
66
-         * }}
67
-         */
68 57
         this.state = {
69
-            /**
70
-             * The URL, if any, with which the app was launched.
71
-             *
72
-             * @type {object|string}
73
-             */
74 58
             url: this.props.url
75 59
         };
76 60
 
@@ -159,3 +143,6 @@ class Root extends Component {
159 143
 
160 144
 // Register the main/root Component.
161 145
 AppRegistry.registerComponent('App', () => Root);
146
+
147
+// Register the incoming call Component.
148
+AppRegistry.registerComponent('IncomingCallApp', () => IncomingCallApp);

正在加载...
取消
保存