├── .gitattributes
├── AcceptView.qml
├── ChatView.qml
├── Contacts.qml
├── FlatButton.qml
├── ImageButton.qml
├── LVChat.pro
├── LVChat.pro.user
├── LVChat.qm
├── LVChat.ts
├── LoginView.qml
├── accessPointModel.cpp
├── accessPointModel.h
├── android
├── AndroidManifest.xml
└── res
│ ├── drawable-hdpi
│ └── icon.png
│ ├── drawable-ldpi
│ └── icon.png
│ ├── drawable-mdpi
│ └── icon.png
│ └── values-zh-rCN
│ └── strings.xml
├── audioplayer.cpp
├── audioplayer.h
├── audiorecorder.cpp
├── audiorecorder.h
├── deployment.pri
├── main.cpp
├── main.qml
├── messengerConnection.cpp
├── messengerConnection.h
├── messengerManager.cpp
├── messengerManager.h
├── pointSizeToPixelSize.cpp
├── pointSizeToPixelSize.h
├── protocol.h
├── qml.qrc
├── res
├── appicon.png
├── chat_from_bg_normal.9.png
├── chat_from_bg_pressed.9.png
├── chat_to_bg_normal.9.png
├── chat_to_bg_pressed.9.png
├── head_0.png
├── head_1.png
├── head_2.png
├── head_3.png
├── head_4.png
├── head_5.png
├── head_6.png
├── head_7.png
├── head_8.png
├── head_9.png
├── ic_accept.png
├── ic_back - 副本.png
├── ic_back.png
├── ic_back_pressed.png
├── ic_chat.png
├── ic_chat_pressed.png
├── ic_microphone.png
├── ic_ok.png
├── ic_ok_pressed.png
├── ic_reject.png
├── ic_search.png
├── ic_search_pressed.png
├── ic_voice.png
└── liao.tsp
├── scaleableBorder.qml
├── scanner.cpp
├── scanner.h
├── screenshots
├── pc_init.png
├── pc_select_head.png
├── scaleableBorder.png
├── 豌豆荚截图20140923214402.png
├── 豌豆荚截图20140923214424.png
├── 豌豆荚截图20140923214450.png
├── 豌豆荚截图20140923214619.png
├── 豌豆荚截图20140923214651.png
├── 豌豆荚截图20140923214816.png
├── 豌豆荚截图20140923214838.png
├── 豌豆荚截图20140923214854.png
├── 豌豆荚截图20140923222600.png
└── 豌豆荚截图20140923222705.png
└── voiceMessage.h
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/AcceptView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 |
3 | Rectangle {
4 | id: acceptView;
5 | anchors.fill: parent;
6 | implicitWidth: 320;
7 | implicitHeight: 240;
8 | color: "darkgray";
9 | property alias text: content.text;
10 | signal accepted();
11 | signal rejected();
12 |
13 | Text {
14 | id: content;
15 | anchors.centerIn: parent;
16 | verticalAlignment: Text.AlignVCenter;
17 | horizontalAlignment: Text.AlignHCenter;
18 | font.pointSize: 24;
19 | height: 10 + fontUtil.height(24);
20 | wrapMode: Text.WordWrap;
21 | }
22 |
23 | Row {
24 | anchors.horizontalCenter: parent.horizontalCenter;
25 | anchors.bottom: parent.bottom;
26 | anchors.bottomMargin: 12;
27 | spacing: 8;
28 |
29 | FlatButton {
30 | iconWidth: 48;
31 | iconHeight: 48;
32 | width: 60 + fontUtil.width(20, text);
33 | height: Math.max(48, 8 + fontUtil.height(20));
34 | iconSource: "res/ic_accept.png";
35 | font.pointSize: 20;
36 | text: qsTr("Accept");
37 | onClicked: acceptView.accepted();
38 | }
39 | FlatButton{
40 | iconWidth: 48;
41 | iconHeight: 48;
42 | width: 60 + fontUtil.width(20, text);
43 | height: Math.max(48, 8 + fontUtil.height(20));
44 | iconSource: "res/ic_reject.png";
45 | font.pointSize: 20;
46 | text: qsTr("Reject");
47 | onClicked: acceptView.rejected();
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ChatView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 | import QtQuick.Controls 1.2
3 | import QtQuick.Layouts 1.1
4 | import QtQuick.Controls.Styles 1.2
5 | import QtMultimedia 5.0
6 |
7 | Item {
8 | id: chat;
9 | anchors.fill: parent;
10 | signal finished();
11 | objectName: "chat";
12 | property alias nick: nickName.text;
13 | property int peerPortraitIndex: 9;
14 | property int portraitIndex: 9;
15 | Rectangle {
16 | id: title;
17 | width: parent.width;
18 | height: Math.max(48, 8 + fontUtil.height(20));
19 | color: "#7378ba"; //"#203050";
20 | ImageButton {
21 | id: back;
22 | anchors.left: parent.left;
23 | anchors.verticalCenter: parent.verticalCenter;
24 | anchors.margins: 4;
25 | width: 48;
26 | height: 48;
27 | z: 2;
28 | iconNormal: "res/ic_back.png";
29 | iconPressed: "res/ic_back_pressed.png";
30 | onClicked: chat.finished();
31 | }
32 | Text {
33 | id: nickName;
34 | anchors.left: back.right;
35 | anchors.leftMargin: 6;
36 | anchors.right: parent.right;
37 | anchors.verticalCenter: back.verticalCenter;
38 | verticalAlignment: Text.AlignVCenter;
39 | font.pointSize: 20;
40 | color: "white";
41 | }
42 | }
43 |
44 | TextField {
45 | id: recordState;
46 | width: parent.width - 40;
47 | height: 20 + fontUtil.height(24);
48 | anchors.centerIn: parent;
49 | z: 10;
50 | readOnly: true;
51 | visible: false;
52 | horizontalAlignment: TextInput.AlignHCenter;
53 | verticalAlignment: TextInput.AlignVCenter;
54 | style: TextFieldStyle {
55 | textColor: "red";
56 | background: Rectangle {
57 | color: "lightgray";
58 | radius: 2
59 | border.color: "#444444";
60 | border.width: 1
61 | }
62 | }
63 |
64 | function onDurationChanged(duration){
65 | text = "%1\"".arg(Math.ceil(duration/1000));
66 | }
67 | states:[
68 | State {
69 | name: "hide";
70 | PropertyChanges{
71 | target: recordState;
72 | restoreEntryValues: false;
73 | opacity: 0;
74 | }
75 | },
76 | State {
77 | name: "show";
78 | PropertyChanges{
79 | target: recordState;
80 | restoreEntryValues: false;
81 | opacity: 1;
82 | visible: true;
83 | }
84 | }
85 | ]
86 | state: "hide";
87 | transitions: [
88 | Transition {
89 | from: "hide";
90 | to: "show";
91 | NumberAnimation {
92 | property: "opacity";
93 | duration: 800;
94 | }
95 | },
96 | Transition {
97 | from: "show";
98 | to: "hide";
99 | SequentialAnimation {
100 | NumberAnimation {
101 | properties: "opacity";
102 | duration: 1500;
103 | }
104 |
105 | PropertyAction {
106 | target: recordState;
107 | property: "visible";
108 | value: false;
109 | }
110 | }
111 | }
112 | ]
113 | }
114 |
115 | Connections {
116 | target: audioRecorder;
117 | onDurationChanged: recordState.onDurationChanged(duration);
118 | }
119 |
120 | Rectangle {
121 | id: pressTalk;
122 | anchors.horizontalCenter: parent.horizontalCenter;
123 | anchors.bottom: parent.bottom;
124 | anchors.bottomMargin: 4;
125 | property string text: qsTr("Press & Talk");
126 | property bool pressed: false;
127 | height: Math.max(8 + fontUtil.height(20), 50);
128 | width: 64 + fontUtil.width(20, text);
129 | border.color: "gray";
130 | border.width: 1;
131 | radius: 4;
132 | color: pressTalk.pressed ? "lightgray" : "gray";
133 | Image {
134 | id: microPhone;
135 | anchors.left: parent.left;
136 | anchors.leftMargin: 4;
137 | anchors.verticalCenter: parent.verticalCenter;
138 | width: 48;
139 | height: 48;
140 | source: "res/ic_microphone.png";
141 | }
142 | Text{
143 | anchors.left: microPhone.right;
144 | anchors.leftMargin: 2;
145 | anchors.right: parent.right;
146 | height: parent.height;
147 | font.pointSize: 20;
148 | text: parent.text;
149 | color: parent.pressed ? "blue" : "black";
150 | verticalAlignment: Text.AlignVCenter;
151 | horizontalAlignment: Text.AlignHCenter;
152 | }
153 |
154 | MouseArea {
155 | anchors.fill: parent;
156 | onPressed: {
157 | pressTalk.pressed = true;
158 | audioRecorder.record();
159 | recordState.visible = true;
160 | recordState.text = qsTr("Recording...");
161 | recordState.state = "show";
162 | }
163 | onReleased: {
164 | pressTalk.pressed = false;
165 | audioRecorder.stop();
166 | if(audioRecorder.success()){
167 | chatList.model.append(
168 | {
169 | "direction": 0,
170 | "audioFile": audioRecorder.file,
171 | "duration": Math.round(audioRecorder.duration/1000)
172 | });
173 | console.log("recorded, ", audioRecorder.file);
174 | msgManager.sendVoiceMessage(audioRecorder.file, audioRecorder.duration);
175 | }
176 | recordState.state = "hide";
177 | }
178 | }
179 |
180 | z: 2;
181 | }
182 |
183 | MediaPlayer {
184 | id: player;
185 | }
186 |
187 | ListView {
188 | id: chatList;
189 | anchors.left: parent.left;
190 | anchors.right: parent.right;
191 | anchors.top: title.bottom;
192 | anchors.bottom: pressTalk.top;
193 | anchors.margins: 4;
194 | clip: true;
195 | spacing: 6;
196 | model: ListModel {}
197 | delegate: Item{
198 | width: parent.width;
199 | height: Math.max(52, 8 + fontUtil.height(20));
200 | Image {
201 | id: headPortrait;
202 | source: "res/head_%1.png".arg(direction == 0 ? chat.portraitIndex : chat.peerPortraitIndex);
203 | width: 48;
204 | height: 48;
205 | x: direction == 0 ? parent.width - width -2 : 2;
206 | y: 2;
207 | }
208 |
209 | BorderImage {
210 | id: msgWrapper;
211 | source: direction == 0 ? (pressed ? "res/chat_to_bg_pressed.9.png":"res/chat_to_bg_normal.9.png") : (pressed ? "res/chat_from_bg_pressed.9.png":"res/chat_from_bg_normal.9.png");
212 | width: 140; //TODO: caculate via PPI
213 | height: parent.height;
214 | x: direction == 0 ? headPortrait.x - 2 - width : headPortrait.x + headPortrait.width + 2;
215 | border.left: direction == 0 ? 6 : 16;
216 | border.top: 38;
217 | border.right: direction == 0 ? 16 : 6;
218 | border.bottom: 6;
219 | property bool pressed: false;
220 | property string filePath: audioFile;
221 | Image {
222 | id: voice;
223 | source: "res/ic_voice.png";
224 | mirror: direction == 0;
225 | width: 45;
226 | height: 45;
227 | x: direction == 0 ? parent.width - width - 2 - msgWrapper.border.right : 2 + msgWrapper.border.left;
228 | y: (parent.height - height)/2;
229 | }
230 |
231 | Text {
232 | x: direction == 0 ? 2 : voice.x + voice.width + 4;
233 | y: voice.y;
234 | height: voice.height;
235 | width: parent.width - 24 - voice.width;
236 | text: "%1\"".arg(duration);
237 | color: "black";
238 | horizontalAlignment: direction == 0 ? Text.AlignRight : Text.AlignLeft;
239 | verticalAlignment: Text.AlignVCenter;
240 | font.pointSize: 16;
241 | }
242 |
243 | MouseArea {
244 | anchors.fill: parent;
245 | preventStealing: true;
246 | onPressed: msgWrapper.pressed = true;
247 | onReleased: msgWrapper.pressed = false;
248 | onClicked: {
249 | console.log("audioFile-", msgWrapper.filePath);
250 | player.source = (Qt.platform.os === "windows" ? "file:///" : "file://") + msgWrapper.filePath;
251 | player.play();
252 | }
253 | }
254 | }
255 | }
256 | }
257 |
258 | Connections {
259 | target: msgManager;
260 | onVoiceMessageArrived:{
261 | chatList.model.append(
262 | {
263 | "direction": 1,
264 | "audioFile": fileName,
265 | "duration": Math.round(duration / 1000)
266 | });
267 | }
268 | }
269 |
270 | function changePeer(nickName, portraitIndex){
271 | nick = nickName;
272 | peerPortraitIndex = portraitIndex;
273 | chatList.model.clear();
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/Contacts.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 | import QtQuick.Controls 1.2
3 | import QtQuick.Layouts 1.1
4 |
5 | Item {
6 | id: contacts;
7 | anchors.fill: parent;
8 | signal talkTo(int peerIndex);
9 | signal startScan();
10 | objectName: "contacts";
11 | function scanAccessPoint(){
12 | startScan();
13 | scanButton.enabled = false;
14 | apmodel.scan();
15 | }
16 |
17 | Rectangle {
18 | id: title;
19 | width: parent.width;
20 | height: 48;
21 | color: "#7378ba";
22 | ImageButton {
23 | id: scanButton;
24 | anchors.centerIn: parent;
25 | width: 48;
26 | height: 48;
27 | z: 2;
28 | iconNormal: "res/ic_search.png";
29 | iconPressed: "res/ic_search_pressed.png";
30 | onClicked: scanAccessPoint();
31 | }
32 | }
33 |
34 | ListView {
35 | id: peoples;
36 | anchors.left: parent.left;
37 | anchors.right: parent.right;
38 | anchors.top: title.bottom;
39 | anchors.bottom: requestTalk.top;
40 | anchors.margins: 4;
41 | clip: true;
42 | spacing: 6;
43 | highlight: Rectangle {
44 | width: peoples.width;
45 | color: "lightsteelblue";
46 | }
47 |
48 | model: apmodel;
49 | delegate: Item{
50 | id: wrapper;
51 | width: parent.width;
52 | height: Math.max(52, 8 + fontUtil.height(20));
53 | Image {
54 | id: headPortrait;
55 | source: "res/head_%1.png".arg(portraitIndex);
56 | width: 48;
57 | height: 48;
58 | x: 2;
59 | y: 2;
60 | }
61 |
62 | Text {
63 | id: nick;
64 | font.pointSize: 20;
65 | anchors.left: headPortrait.right;
66 | anchors.leftMargin: 4;
67 | anchors.verticalCenter: headPortrait.verticalCenter;
68 | text: nickName;
69 | }
70 |
71 | MouseArea {
72 | anchors.fill: parent;
73 | onClicked: {
74 | wrapper.ListView.view.currentIndex = index;
75 | }
76 | }
77 | }
78 | }
79 |
80 | Rectangle {
81 | id: requestTalk;
82 | width: parent.width;
83 | height: 64;
84 | anchors.bottom: parent.bottom;
85 | color: "gray"; //"#605550";
86 | ImageButton {
87 | anchors.centerIn: parent;
88 | width: 60;
89 | height: 64;
90 | z: 2;
91 | iconNormal: "res/ic_chat.png";
92 | iconPressed: "res/ic_chat_pressed.png";
93 | onClicked: {
94 | var cur = peoples.currentIndex;
95 | if(cur >= 0){
96 | contacts.talkTo(cur);
97 | }
98 | }
99 | }
100 | }
101 |
102 | Connections {
103 | target: apmodel;
104 | onScanFinished:{
105 | console.log("scanFinished");
106 | scanButton.enabled = true;
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/FlatButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 |
3 | Rectangle {
4 | id: bkgnd;
5 | implicitWidth: 120;
6 | implicitHeight: 50;
7 | color: "transparent";
8 | property alias iconSource: icon.source;
9 | property alias iconWidth: icon.width;
10 | property alias iconHeight: icon.height;
11 | property alias textColor: btnText.color;
12 | property alias font: btnText.font;
13 | property alias text: btnText.text;
14 | radius: 6;
15 | property bool hovered: false;
16 | border.color: "lightgray";
17 | border.width: hovered ? 2 : 1;
18 | signal clicked;
19 |
20 | Image {
21 | id: icon;
22 | anchors.left: parent.left;
23 | anchors.verticalCenter: parent.verticalCenter;
24 | }
25 | Text {
26 | id: btnText;
27 | anchors.left: icon.right;
28 | anchors.verticalCenter: icon.verticalCenter;
29 | anchors.margins: 4;
30 | color: ma.pressed ? "blue" : (parent.hovered ? "#0000a0" : "black");
31 | }
32 | MouseArea {
33 | id: ma;
34 | anchors.fill: parent;
35 | hoverEnabled: true;
36 | onEntered: {
37 | bkgnd.hovered = true;
38 | }
39 | onExited: {
40 | bkgnd.hovered = false;
41 | }
42 | onClicked: {
43 | bkgnd.hovered = false;
44 | bkgnd.clicked();
45 | }
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/ImageButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 |
3 | Rectangle {
4 | id: btn;
5 | implicitWidth: 48;
6 | implicitHeight: 48;
7 | color: "transparent";
8 | property var iconNormal;
9 | property var iconPressed;
10 | signal clicked();
11 | Image {
12 | id: icon;
13 | anchors.fill: parent;
14 | anchors.margins: 1;
15 | fillMode: Image.PreserveAspectFit;
16 | }
17 |
18 | onIconNormalChanged: icon.source = iconNormal;
19 |
20 | MouseArea {
21 | anchors.fill: parent;
22 | onPressed: {
23 | if(btn.iconPressed != undefined){
24 | icon.source = iconPressed;
25 | }else{
26 | border.width = 1;
27 | border.color = "gray";
28 | }
29 | }
30 | onReleased: {
31 | if(btn.iconPressed == undefined){
32 | border.width = 0;
33 | border.color = "gray";
34 | }
35 | icon.source = iconNormal;
36 | }
37 | onClicked: btn.clicked();
38 | }
39 |
40 | Component.onCompleted: {
41 | icon.source = iconNormal;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/LVChat.pro:
--------------------------------------------------------------------------------
1 | TEMPLATE = app
2 |
3 | QT += qml quick network multimedia
4 |
5 | SOURCES += main.cpp \
6 | audiorecorder.cpp \
7 | messengerConnection.cpp \
8 | scanner.cpp \
9 | accessPointModel.cpp \
10 | pointSizeToPixelSize.cpp \
11 | ../stockMonitor/qDebug2Logcat.cpp \
12 | messengerManager.cpp
13 |
14 | RESOURCES += qml.qrc
15 |
16 | # Additional import path used to resolve QML modules in Qt Creator's code model
17 | QML_IMPORT_PATH =
18 |
19 | # Default rules for deployment.
20 | include(deployment.pri)
21 |
22 | OTHER_FILES += \
23 | android/AndroidManifest.xml
24 |
25 | HEADERS += \
26 | audiorecorder.h \
27 | messengerConnection.h \
28 | protocol.h \
29 | scanner.h \
30 | voiceMessage.h \
31 | accessPointModel.h \
32 | pointSizeToPixelSize.h \
33 | ../stockMonitor/qDebug2Logcat.h \
34 | messengerManager.h
35 |
36 | ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
37 |
--------------------------------------------------------------------------------
/LVChat.pro.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ProjectExplorer.Project.ActiveTarget
7 | 0
8 |
9 |
10 | ProjectExplorer.Project.EditorSettings
11 |
12 | true
13 | false
14 | true
15 |
16 | Cpp
17 |
18 | CppGlobal
19 |
20 |
21 |
22 | QmlJS
23 |
24 | QmlJSGlobal
25 |
26 |
27 | 2
28 | UTF-8
29 | false
30 | 4
31 | false
32 | 80
33 | true
34 | true
35 | 1
36 | true
37 | false
38 | 0
39 | true
40 | 0
41 | 8
42 | true
43 | 1
44 | true
45 | true
46 | true
47 | false
48 |
49 |
50 |
51 | ProjectExplorer.Project.PluginSettings
52 |
53 |
54 |
55 | ProjectExplorer.Project.Target.0
56 |
57 | Desktop Qt 5.3 MinGW 32bit
58 | Desktop Qt 5.3 MinGW 32bit
59 | qt.53.win32_mingw482_kit
60 | 0
61 | 0
62 | 0
63 |
64 | D:/projects/build-LVChat-Desktop_Qt_5_3_MinGW_32bit-Debug
65 |
66 |
67 | true
68 | qmake
69 |
70 | QtProjectManager.QMakeBuildStep
71 | false
72 | true
73 |
74 | false
75 |
76 |
77 | true
78 | Make
79 |
80 | Qt4ProjectManager.MakeStep
81 |
82 | false
83 |
84 |
85 |
86 | 2
87 | 构建
88 |
89 | ProjectExplorer.BuildSteps.Build
90 |
91 |
92 |
93 | true
94 | Make
95 |
96 | Qt4ProjectManager.MakeStep
97 |
98 | true
99 | clean
100 |
101 |
102 | 1
103 | 清理
104 |
105 | ProjectExplorer.BuildSteps.Clean
106 |
107 | 2
108 | false
109 |
110 | Debug
111 |
112 | Qt4ProjectManager.Qt4BuildConfiguration
113 | 2
114 | true
115 |
116 |
117 | D:/projects/build-LVChat-Desktop_Qt_5_3_MinGW_32bit-Release
118 |
119 |
120 | true
121 | qmake
122 |
123 | QtProjectManager.QMakeBuildStep
124 | false
125 | true
126 |
127 | false
128 |
129 |
130 | true
131 | Make
132 |
133 | Qt4ProjectManager.MakeStep
134 |
135 | false
136 |
137 |
138 |
139 | 2
140 | 构建
141 |
142 | ProjectExplorer.BuildSteps.Build
143 |
144 |
145 |
146 | true
147 | Make
148 |
149 | Qt4ProjectManager.MakeStep
150 |
151 | true
152 | clean
153 |
154 |
155 | 1
156 | 清理
157 |
158 | ProjectExplorer.BuildSteps.Clean
159 |
160 | 2
161 | false
162 |
163 | Release
164 |
165 | Qt4ProjectManager.Qt4BuildConfiguration
166 | 0
167 | true
168 |
169 | 2
170 |
171 |
172 | 0
173 | 部署
174 |
175 | ProjectExplorer.BuildSteps.Deploy
176 |
177 | 1
178 | 在本地部署
179 |
180 | ProjectExplorer.DefaultDeployConfiguration
181 |
182 | 1
183 |
184 |
185 |
186 | false
187 | false
188 | false
189 | false
190 | true
191 | 0.01
192 | 10
193 | true
194 | 1
195 | 25
196 |
197 | 1
198 | true
199 | false
200 | true
201 | valgrind
202 |
203 | 0
204 | 1
205 | 2
206 | 3
207 | 4
208 | 5
209 | 6
210 | 7
211 | 8
212 | 9
213 | 10
214 | 11
215 | 12
216 | 13
217 | 14
218 |
219 | 2
220 |
221 | LVChat
222 |
223 | Qt4ProjectManager.Qt4RunConfiguration:D:/projects/LVChat/LVChat.pro
224 |
225 | LVChat.pro
226 | false
227 | false
228 |
229 | 3768
230 | false
231 | true
232 | false
233 | false
234 | true
235 |
236 | 1
237 |
238 |
239 |
240 | ProjectExplorer.Project.Target.1
241 |
242 | Android for armeabi-v7a (GCC 4.7, Qt 5.3.1)
243 | Android for armeabi-v7a (GCC 4.7, Qt 5.3.1)
244 | {2a41e2ec-b5ed-4084-bb4e-fa614a1a62c4}
245 | 0
246 | 0
247 | 0
248 |
249 | D:/projects/build-LVChat-Android_for_armeabi_v7a_GCC_4_7_Qt_5_3_1-Release
250 |
251 |
252 | true
253 | qmake
254 |
255 | QtProjectManager.QMakeBuildStep
256 | false
257 | true
258 |
259 | false
260 |
261 |
262 | true
263 | Make
264 |
265 | Qt4ProjectManager.MakeStep
266 |
267 | -w
268 | -r
269 |
270 | false
271 |
272 |
273 |
274 | 2
275 | 构建
276 |
277 | ProjectExplorer.BuildSteps.Build
278 |
279 |
280 |
281 | true
282 | Make
283 |
284 | Qt4ProjectManager.MakeStep
285 |
286 | -w
287 | -r
288 |
289 | true
290 | clean
291 |
292 |
293 | 1
294 | 清理
295 |
296 | ProjectExplorer.BuildSteps.Clean
297 |
298 | 2
299 | false
300 |
301 | Release
302 |
303 | Qt4ProjectManager.Qt4BuildConfiguration
304 | 0
305 | true
306 |
307 | 1
308 |
309 |
310 |
311 | 1
312 | true
313 | 复制应用程序的数据
314 |
315 | Qt4ProjectManager.AndroidPackageInstallationStep
316 |
317 |
318 | android-17
319 |
320 | D:/projects/LVChat/LVChat.pro
321 | true
322 | Deploy to Android device
323 |
324 | Qt4ProjectManager.AndroidDeployQtStep
325 | 1
326 | false
327 | false
328 |
329 | 2
330 | 部署
331 |
332 | ProjectExplorer.BuildSteps.Deploy
333 |
334 | 1
335 | 部署到Android设备
336 | 部署到Android设备
337 | Qt4ProjectManager.AndroidDeployConfiguration2
338 |
339 | 1
340 |
341 |
342 |
343 | false
344 | false
345 | false
346 | false
347 | true
348 | 0.01
349 | 10
350 | true
351 | 1
352 | 25
353 |
354 | 1
355 | true
356 | false
357 | true
358 | valgrind
359 |
360 | 0
361 | 1
362 | 2
363 | 3
364 | 4
365 | 5
366 | 6
367 | 7
368 | 8
369 | 9
370 | 10
371 | 11
372 | 12
373 | 13
374 | 14
375 |
376 | 在Android设备上运行
377 |
378 | Qt4ProjectManager.AndroidRunConfiguration:D:/projects/LVChat/LVChat.pro
379 | LVChat.pro
380 | 3768
381 | false
382 | true
383 | false
384 | false
385 | true
386 |
387 | 1
388 |
389 |
390 |
391 | ProjectExplorer.Project.TargetCount
392 | 2
393 |
394 |
395 | ProjectExplorer.Project.Updater.EnvironmentId
396 | {6730cd59-2fb0-4d8d-9548-39afc1844ae8}
397 |
398 |
399 | ProjectExplorer.Project.Updater.FileVersion
400 | 15
401 |
402 |
403 |
--------------------------------------------------------------------------------
/LVChat.qm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/LVChat.qm
--------------------------------------------------------------------------------
/LVChat.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AcceptView
6 |
7 |
8 | Accept
9 | 接受
10 |
11 |
12 |
13 | Reject
14 | 拒绝
15 |
16 |
17 |
18 | ChatView
19 |
20 |
21 | Press & Talk
22 | 按住说话
23 |
24 |
25 |
26 | Recording...
27 | 录音中……
28 |
29 |
30 |
31 | main
32 |
33 |
34 | LVChat
35 | 聊哈
36 |
37 |
38 |
39 | want to talk with you.
40 | want to talk with you.
41 | 想和你聊两句。
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/LoginView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 | import QtQuick.Controls 1.2
3 | import QtQuick.Layouts 1.1
4 |
5 | Item {
6 | id: login;
7 | anchors.fill: parent;
8 | anchors.margins: 10;
9 | implicitWidth: 300;
10 | implicitHeight: 102;
11 | signal go(string nick, int portraitIndex);
12 | property int portraitIndex: 9;
13 | RowLayout {
14 | anchors.fill: parent;
15 | spacing: 2;
16 | ImageButton {
17 | id: headPortrait;
18 | width: 48;
19 | height: 48;
20 | Layout.alignment: Qt.AlignVCenter;
21 | iconNormal: "res/head_9.png";
22 | function onHeadPortraitSelected(index){
23 | login.portraitIndex = index;
24 | iconNormal = "res/head_%1.png".arg(index);
25 | }
26 | onClicked: login.showHeadPortraits(onHeadPortraitSelected);
27 | }
28 | Rectangle {
29 | border.width: 1;
30 | border.color: "gray";
31 | Layout.fillWidth: true;
32 | Layout.alignment: Qt.AlignVCenter;
33 | height: 12 + fontUtil.height(22);
34 | color: "transparent";
35 | TextInput {
36 | anchors.margins: 2;
37 | anchors.fill: parent;
38 | id: nickEditor;
39 | text: "nickname";
40 | font.pointSize: 22;
41 | verticalAlignment: TextInput.AlignVCenter;
42 | }
43 | }
44 | ImageButton {
45 | width: 48;
46 | height: 48;
47 | Layout.alignment: Qt.AlignVCenter;
48 | iconNormal: "res/ic_ok.png";
49 | iconPressed: "res/ic_ok_pressed.png";
50 | onClicked: login.go(nickEditor.text, login.portraitIndex);
51 | }
52 | }
53 |
54 | property var selector: null;
55 | function showHeadPortraits(fn){
56 | selector = headComp.createObject(login,
57 | {
58 | "z":2,
59 | "callback":fn,
60 | "width": width
61 | });
62 | selector.selected.connect(onHeadPanelClosed);
63 | }
64 | function onHeadPanelClosed(){
65 | selector.destroy();
66 | selector = null;
67 | }
68 |
69 | Component {
70 | id: headComp;
71 | Rectangle {
72 | id: wrapper;
73 | property var callback;
74 | signal selected();
75 | anchors.centerIn: parent;
76 | implicitWidth: 260;
77 | implicitHeight: 104;
78 | color: "lightgray";
79 | border.width: 1;
80 | border.color: "gray";
81 | radius: 4;
82 | Grid {
83 | id: headPortraits;
84 | anchors.centerIn: parent;
85 | anchors.margins: 2;
86 | columns: 5;
87 | columnSpacing: 2;
88 | rows: 2;
89 | rowSpacing: 2;
90 |
91 | Repeater {
92 | delegate: ImageButton{
93 | width: 48;
94 | height: 48;
95 | iconNormal: "res/head_%1.png".arg(index);
96 | onClicked: {
97 | wrapper.callback(index);
98 | wrapper.selected();
99 | wrapper.visible = false;
100 | }
101 | }
102 | model: 10;
103 | }
104 | }
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/accessPointModel.cpp:
--------------------------------------------------------------------------------
1 | #include "accessPointModel.h"
2 | #include
3 | #include "scanner.h"
4 | #include "messengerConnection.h"
5 | #include
6 | #include "messengerManager.h"
7 |
8 | typedef QVector VideoData;
9 | class AccessPointModelPrivate
10 | {
11 | public:
12 | AccessPointModelPrivate()
13 | : m_current(0)
14 | {
15 | int role = Qt::UserRole;
16 | m_roleNames.insert(role++, "nickName");
17 | m_roleNames.insert(role++, "portraitIndex");
18 | m_roleNames.insert(role++, "address");
19 | }
20 |
21 | ~AccessPointModelPrivate()
22 | {
23 | clear();
24 | }
25 |
26 | void clear()
27 | {
28 | int count = m_accessPoints.size();
29 | if(count > 0)
30 | {
31 | for(int i = 0; i < count; i++)
32 | {
33 | delete m_accessPoints.at(i);
34 | }
35 | m_accessPoints.clear();
36 | }
37 | }
38 |
39 | QHash m_roleNames;
40 | QVector m_accessPoints;
41 | AccessPointScanner m_scanner;
42 | int m_current;
43 | };
44 |
45 | AccessPointModel::AccessPointModel(QObject *parent)
46 | : QAbstractListModel(parent)
47 | , m_dptr(new AccessPointModelPrivate)
48 | {
49 | connect(&(m_dptr->m_scanner), SIGNAL(scanFinished()),
50 | this, SIGNAL(scanFinished()));
51 | connect(&(m_dptr->m_scanner), SIGNAL(newAccessPoint(AccessPoint*)),
52 | this, SLOT(onNewAccessPoint(AccessPoint*)));
53 | }
54 |
55 | AccessPointModel::~AccessPointModel()
56 | {
57 | delete m_dptr;
58 | }
59 |
60 | int AccessPointModel::rowCount(const QModelIndex &parent) const
61 | {
62 | return m_dptr->m_accessPoints.size();
63 | }
64 |
65 | QVariant AccessPointModel::data(const QModelIndex &index, int role) const
66 | {
67 | AccessPoint *d = m_dptr->m_accessPoints[index.row()];
68 | switch(role - Qt::UserRole)
69 | {
70 | case 0:
71 | return d->m_nickName;
72 | case 1:
73 | return d->m_portraitIndex;
74 | case 2:
75 | return d->m_address.toString();
76 | default:
77 | return QVariant();
78 | }
79 | }
80 |
81 | QHash AccessPointModel::roleNames() const
82 | {
83 | return m_dptr->m_roleNames;
84 | }
85 |
86 | void AccessPointModel::scan()
87 | {
88 | beginResetModel();
89 |
90 | m_dptr->clear();
91 | m_dptr->m_scanner.startScan();
92 |
93 | endResetModel();
94 | }
95 |
96 | void AccessPointModel::talkTo(int index)
97 | {
98 | m_dptr->m_current = index;
99 | MessengerManager::instance()->talkTo(m_dptr->m_accessPoints.at(index));
100 | }
101 |
102 | QString AccessPointModel::currentNick()
103 | {
104 | AccessPoint *ap = m_dptr->m_accessPoints.at(m_dptr->m_current);
105 | return ap->m_nickName;
106 | }
107 |
108 | int AccessPointModel::currentPortraitIndex()
109 | {
110 | AccessPoint *ap = m_dptr->m_accessPoints.at(m_dptr->m_current);
111 | return ap->m_portraitIndex;
112 | }
113 |
114 | void AccessPointModel::onNewAccessPoint(AccessPoint *ap)
115 | {
116 | int count = m_dptr->m_accessPoints.size();
117 | beginInsertRows(QModelIndex(), count, count);
118 | m_dptr->m_accessPoints.append(ap);
119 | endInsertRows();
120 | }
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/accessPointModel.h:
--------------------------------------------------------------------------------
1 | #ifndef AccessPointModel_H
2 | #define AccessPointModel_H
3 | #include
4 |
5 | class AccessPoint;
6 | class AccessPointModelPrivate;
7 | class AccessPointModel : public QAbstractListModel
8 | {
9 | Q_OBJECT
10 | Q_PROPERTY(QString currentNick READ currentNick)
11 | Q_PROPERTY(int currentPortraitIndex READ currentPortraitIndex)
12 | public:
13 | AccessPointModel(QObject *parent = 0);
14 | ~AccessPointModel();
15 |
16 | int rowCount(const QModelIndex &parent) const;
17 | QVariant data(const QModelIndex &index, int role) const;
18 | QHash roleNames() const;
19 |
20 | Q_INVOKABLE void scan();
21 | Q_INVOKABLE void talkTo(int index);
22 | QString currentNick();
23 | int currentPortraitIndex();
24 |
25 | signals:
26 | void scanFinished();
27 |
28 | private slots:
29 | void onNewAccessPoint(AccessPoint *ap);
30 |
31 | private:
32 | AccessPointModelPrivate *m_dptr;
33 | };
34 |
35 | #endif // AccessPointModel_H
36 |
--------------------------------------------------------------------------------
/android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
43 |
44 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/android/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/android/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/android/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/android/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/android/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/android/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/android/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 聊哈
4 | 无法找到Ministro服务。\n应用程序无法启动。
5 | 此应用程序需要Ministro服务。您想安装它吗?
6 | 您的应用程序遇到一个致命错误导致它无法继续。
7 |
8 |
--------------------------------------------------------------------------------
/audioplayer.cpp:
--------------------------------------------------------------------------------
1 | #include "audioplayer.h"
2 | #include
3 |
4 | SimpleAudioPlayer::SimpleAudioPlayer()
5 | {
6 | m_player = new QMediaPlayer(this);
7 | connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))
8 | ,this, SLOT(onMediaStatusChanged(QMediaPlayer::MediaStatus)));
9 | connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)),
10 | this, SLOT(onStateChanged(QMediaPlayer::State)));
11 | connect(m_player, SIGNAL(error(QMediaPlayer::Error)),
12 | this, SLOT(onError(QMediaPlayer::Error)));
13 |
14 | m_playlist = new QMediaPlaylist(this);
15 | m_player->setPlaylist(m_playlist);
16 | }
17 |
18 | SimpleAudioPlayer::~SimpleAudioPlayer()
19 | {
20 |
21 | }
22 |
23 | void SimpleAudioPlayer::play(const QString &filePath)
24 | {
25 | qDebug() << "try to play - " << filePath;
26 | stop();
27 | m_playlist->addMedia(QUrl::fromLocalFile(filePath));
28 | m_playlist->setCurrentIndex(0);
29 | m_player->play();
30 | }
31 |
32 | void SimpleAudioPlayer::stop()
33 | {
34 | m_player->stop();
35 | m_playlist->clear();
36 | }
37 |
38 | void SimpleAudioPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus status)
39 | {
40 | switch(status)
41 | {
42 | case QMediaPlayer::EndOfMedia:
43 | qDebug() << "EndOfMedia";
44 | emit playFinished();
45 | break;
46 | case QMediaPlayer::InvalidMedia:
47 | qDebug() << "invalidMedia";
48 | emit playError();
49 | break;
50 | case QMediaPlayer::BufferingMedia:
51 | break;
52 | case QMediaPlayer::BufferedMedia:
53 | break;
54 | case QMediaPlayer::LoadingMedia:
55 | break;
56 | case QMediaPlayer::LoadedMedia:
57 | break;
58 | }
59 | }
60 |
61 | void SimpleAudioPlayer::onError(QMediaPlayer::Error error)
62 | {
63 | qDebug() << "play error " << error;
64 | emit playError();
65 | }
66 |
67 | void SimpleAudioPlayer::onStateChanged(QMediaPlayer::State state)
68 | {
69 |
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/audioplayer.h:
--------------------------------------------------------------------------------
1 | #ifndef AUDIOPLAYER_H
2 | #define AUDIOPLAYER_H
3 |
4 | #include
5 | #include
6 |
7 | class SimpleAudioPlayer : public QObject
8 | {
9 | Q_OBJECT
10 | public:
11 | SimpleAudioPlayer();
12 | ~SimpleAudioPlayer();
13 |
14 | Q_INVOKABLE void play(const QString &filePath);
15 | Q_INVOKABLE void stop();
16 |
17 | signals:
18 | void playError();
19 | void playFinished();
20 |
21 | protected slots:
22 | void onMediaStatusChanged(QMediaPlayer::MediaStatus status);
23 | void onError(QMediaPlayer::Error error);
24 | void onStateChanged(QMediaPlayer::State state);
25 |
26 | private:
27 | QMediaPlayer *m_player;
28 | QMediaPlaylist *m_playlist;
29 | };
30 |
31 | #endif // AUDIOPLAYER_H
32 |
--------------------------------------------------------------------------------
/audiorecorder.cpp:
--------------------------------------------------------------------------------
1 | #include "audiorecorder.h"
2 | #include
3 | #include
4 | #include
5 | /*
6 | * 05-28 13:54:39.281 27785 27847 D LanVoiceMessenger: inputs: ("default", "mic", "voice_uplink", "voi
7 | ce_downlink", "voice_call", "voice_recognition")
8 | 05-28 13:54:39.281 27785 27847 D LanVoiceMessenger: codecs: ("amr-nb", "amr-wb", "aac")
9 | 05-28 13:54:39.281 27785 27847 D LanVoiceMessenger: containers: ("mp4", "3gp", "amr", "awb")
10 | 05-28 13:54:39.291 27785 27847 D LanVoiceMessenger: samplerates: (8000, 11025, 12000, 16000, 22050,
11 | 24000, 32000, 44100, 48000, 96000)
12 | */
13 | SimpleAudioRecorder::SimpleAudioRecorder(const QString &saveDir)
14 | : m_baseDir(saveDir), m_recorder(this), m_bValid(true)
15 | , m_duration(0), m_bSuccess(true)
16 | {
17 | if(!m_baseDir.endsWith('/')) m_baseDir.append('/');
18 | m_bValid = setupRecorder();
19 | m_bSuccess = m_bValid;
20 | }
21 |
22 | SimpleAudioRecorder::~SimpleAudioRecorder()
23 | {
24 |
25 | }
26 |
27 | void SimpleAudioRecorder::record()
28 | {
29 | if(m_bValid && m_recorder.state() == QMediaRecorder::StoppedState)
30 | {
31 | m_duration = 0;
32 | m_bSuccess = true;
33 |
34 | m_filePath = QString("%1%2.%3")
35 | .arg(m_baseDir)
36 | .arg(QDateTime::currentDateTime().toString("yyMMdd-hhmmss"))
37 | .arg(m_recorder.containerFormat());
38 | m_recorder.setOutputLocation(QUrl::fromLocalFile(m_filePath));
39 | m_recorder.record();
40 | }
41 | }
42 |
43 | void SimpleAudioRecorder::stop()
44 | {
45 | if(m_bValid)
46 | {
47 | m_recorder.stop();
48 | QFile::setPermissions(m_filePath, QFile::ReadOwner | QFile::WriteOwner | QFile::ReadOther | QFile::ReadUser | QFile::ReadGroup);
49 | }
50 | }
51 |
52 | QString SimpleAudioRecorder::file()
53 | {
54 | return m_filePath;
55 | }
56 |
57 | qint64 SimpleAudioRecorder::duration()
58 | {
59 | return m_duration;
60 | }
61 |
62 | bool SimpleAudioRecorder::success()
63 | {
64 | return m_bSuccess;
65 | }
66 |
67 | void SimpleAudioRecorder::onDurationChanged(qint64 duration)
68 | {
69 | m_duration = duration;
70 | emit durationChanged(duration);
71 | }
72 |
73 | void SimpleAudioRecorder::onStatusChanged(QMediaRecorder::Status)
74 | {
75 |
76 | }
77 |
78 | void SimpleAudioRecorder::onError(QMediaRecorder::Error e)
79 | {
80 | if(e != QMediaRecorder::NoError) m_bSuccess = false;
81 | }
82 |
83 | bool SimpleAudioRecorder::setupRecorder()
84 | {
85 | //inputs
86 | QStringList inputs = m_recorder.audioInputs();
87 | if(inputs.size() == 0) return false;
88 | qDebug() << "inputs: " << inputs;
89 | m_recorder.setAudioInput("default");
90 |
91 | //audio codecs;
92 | QStringList codecs = m_recorder.supportedAudioCodecs();
93 | if(codecs.size() == 0) return false;
94 | qDebug() << "codecs: " << codecs;
95 | int sampleRate = 16000;
96 | if(codecs.contains("aac"))
97 | {
98 | m_settings.setCodec("aac");
99 | }
100 | else if(codecs.contains("amr-wb"))
101 | {
102 | m_settings.setCodec("amr-wb");
103 | }
104 | else
105 | {
106 | m_settings.setCodec(codecs.at(0));
107 | sampleRate = 8000;
108 | }
109 | qDebug() << "set codec: " << m_settings.codec();
110 |
111 | //containers
112 | QStringList containers = m_recorder.supportedContainers();
113 | if(containers.size() == 0) return false;
114 | qDebug() << "containers: " << containers;
115 | if(containers.contains("3gp"))
116 | {
117 | m_container = "3gp";
118 | }
119 | else if(containers.contains("mp4"))
120 | {
121 | m_container = "mp4";
122 | }
123 | else
124 | {
125 | m_container = containers.at(0);
126 | }
127 |
128 | //sample rate
129 | QList sampleRates = m_recorder.supportedAudioSampleRates();
130 | if(sampleRates.size() == 0) return false;
131 | qDebug() << "samplerates: " << sampleRates;
132 | if(sampleRates.size() && !sampleRates.contains(sampleRate))
133 | {
134 | sampleRate = sampleRates.at(0);
135 | }
136 |
137 | m_settings.setChannelCount(2);
138 | m_settings.setSampleRate(sampleRate);
139 | m_settings.setQuality(QMultimedia::NormalQuality);
140 | m_settings.setBitRate(64000);
141 | m_settings.setEncodingMode(QMultimedia::AverageBitRateEncoding);
142 | m_recorder.setEncodingSettings(m_settings
143 | , QVideoEncoderSettings()
144 | , m_container);
145 |
146 | connect(&m_recorder, SIGNAL(durationChanged(qint64)), this,
147 | SLOT(onDurationChanged(qint64)));
148 | connect(&m_recorder, SIGNAL(statusChanged(QMediaRecorder::Status)), this,
149 | SLOT(onStatusChanged(QMediaRecorder::Status)));
150 | connect(&m_recorder, SIGNAL(error(QMediaRecorder::Error)), this,
151 | SLOT(onError(QMediaRecorder::Error)));
152 |
153 | return true;
154 | }
155 |
156 |
--------------------------------------------------------------------------------
/audiorecorder.h:
--------------------------------------------------------------------------------
1 | #ifndef AUDIORECORDER_H
2 | #define AUDIORECORDER_H
3 | #include
4 | #include
5 | #include
6 |
7 | class SimpleAudioRecorder : public QObject
8 | {
9 | Q_OBJECT
10 | Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged)
11 | Q_PROPERTY(QString file READ file)
12 | public:
13 | SimpleAudioRecorder(const QString &saveDir);
14 | ~SimpleAudioRecorder();
15 |
16 | Q_INVOKABLE bool isValid()
17 | {
18 | return m_bValid;
19 | }
20 |
21 | QString device()
22 | {
23 | return m_recorder.audioInput();
24 | }
25 |
26 | QString codec()
27 | {
28 | return m_settings.codec();
29 | }
30 |
31 | int sampleRate()
32 | {
33 | return m_settings.sampleRate();
34 | }
35 |
36 | QString container()
37 | {
38 | return m_recorder.containerFormat();
39 | }
40 |
41 | int bitrate()
42 | {
43 | return m_settings.bitRate();
44 | }
45 |
46 | int channels()
47 | {
48 | return m_settings.channelCount();
49 | }
50 |
51 | QMultimedia::EncodingQuality quality()
52 | {
53 | return m_settings.quality();
54 | }
55 |
56 | /*
57 | * current record
58 | */
59 | Q_INVOKABLE void record();
60 | Q_INVOKABLE void stop();
61 | /* return the full path of the current record */
62 | QString file();
63 | qint64 duration();
64 | Q_INVOKABLE bool success();
65 | signals:
66 | void durationChanged(qint64 duration);
67 |
68 | protected slots:
69 | void onDurationChanged(qint64 duration);
70 | void onStatusChanged(QMediaRecorder::Status);
71 | void onError(QMediaRecorder::Error);
72 |
73 | private:
74 | bool setupRecorder();
75 |
76 | private:
77 | QString m_baseDir;
78 | QAudioRecorder m_recorder;
79 | QAudioEncoderSettings m_settings;
80 | bool m_bValid;
81 | QString m_device;
82 | QString m_container;
83 | qint64 m_duration;
84 | bool m_bSuccess;
85 | QString m_filePath;
86 | };
87 |
88 | #endif // AUDIORECORDER_H
89 |
--------------------------------------------------------------------------------
/deployment.pri:
--------------------------------------------------------------------------------
1 | android-no-sdk {
2 | target.path = /data/user/qt
3 | export(target.path)
4 | INSTALLS += target
5 | } else:android {
6 | x86 {
7 | target.path = /libs/x86
8 | } else: armeabi-v7a {
9 | target.path = /libs/armeabi-v7a
10 | } else {
11 | target.path = /libs/armeabi
12 | }
13 | export(target.path)
14 | INSTALLS += target
15 | } else:unix {
16 | isEmpty(target.path) {
17 | qnx {
18 | target.path = /tmp/$${TARGET}/bin
19 | } else {
20 | target.path = /opt/$${TARGET}/bin
21 | }
22 | export(target.path)
23 | }
24 | INSTALLS += target
25 | }
26 |
27 | export(INSTALLS)
28 |
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "messengerManager.h"
7 | #include "accessPointModel.h"
8 | #include "pointSizeToPixelSize.h"
9 | #include "../stockMonitor/qDebug2Logcat.h"
10 | #include "audiorecorder.h"
11 | #include
12 | #include
13 |
14 | int main(int argc, char *argv[])
15 | {
16 | installLogcatMessageHandler("LVChat");
17 | QGuiApplication app(argc, argv);
18 | QIcon icon(":/res/appicon.png");
19 | app.setWindowIcon(icon);
20 | QTranslator ts;
21 | ts.load(":/LVChat.qm");
22 | app.installTranslator(&ts);
23 | MessengerManager *mgr = MessengerManager::instance();
24 | SimpleAudioRecorder *recorder = new SimpleAudioRecorder(QDir::currentPath());
25 | QQmlApplicationEngine engine;
26 | QQmlContext *ctx = engine.rootContext();
27 | ctx->setContextProperty("msgManager", mgr);
28 | ctx->setContextProperty("apmodel", new AccessPointModel);
29 | ctx->setContextProperty("fontUtil", new PointSizeToPixelSize);
30 | ctx->setContextProperty("audioRecorder", recorder);
31 | engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
32 |
33 | return app.exec();
34 | }
35 |
--------------------------------------------------------------------------------
/main.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 | import QtQuick.Window 2.1
3 | import QtQuick.Controls 1.2
4 |
5 | Window {
6 | id: root;
7 | visible: true;
8 | width: 320;
9 | height: 480;
10 | title: qsTr("LVChat");
11 | color: "darkgray";
12 | property var currentView: null;
13 | property string peerNickName;
14 | property int portraitIndex: 9;
15 | property int peerPortraitIndex: 9;
16 | property string queuedPeerNickName;
17 | property int queuedPeerPortraitIndex: 9;
18 | function onBack(){
19 | if(currentView.objectName == "talk") {
20 | currentView.back();
21 | }else{
22 | Qt.quit();
23 | }
24 | }
25 |
26 | function populateContacts(){
27 | currentView.destroy();
28 | var contactComp = Qt.createComponent("Contacts.qml");
29 | currentView = contactComp.createObject(root);
30 | currentView.talkTo.connect(onTalkTo);
31 | currentView.startScan.connect(onStartScan);
32 | }
33 |
34 | function onLogin(nickName, index){
35 | portraitIndex = index;
36 | msgManager.start(nickName, index);
37 | populateContacts();
38 | currentView.scanAccessPoint();
39 | }
40 |
41 | function onTalkTo(peerIndex){
42 | peerNickName = apmodel.currentNick;
43 | peerPortraitIndex = apmodel.currentPortraitIndex;
44 | apmodel.talkTo(peerIndex);
45 | currentView.enabled = false;
46 | waiting.running = true;
47 | }
48 |
49 | /* relative to talkTo
50 | *
51 | */
52 | function onAccepted(){
53 | waiting.running = false;
54 | currentView.destroy();
55 | var chatComp = Qt.createComponent("ChatView.qml");
56 | currentView = chatComp.createObject(root,
57 | {
58 | "nick": root.peerNickName,
59 | "peerPortraitIndex": root.peerPortraitIndex,
60 | "portraitIndex": root.portraitIndex
61 | });
62 | currentView.finished.connect(onChatFinished);
63 | }
64 |
65 | function onRejected(){
66 | if(currentView.objectName == "contacts"){
67 | currentView.enabled = true;
68 | waiting.running = false;
69 | //TODO: show sorry
70 | }else if(currentView.objectName == "chat"){
71 | onChatFinished();
72 | }
73 | }
74 |
75 | function onChatFinished(){
76 | msgManager.closeCurrentSession();
77 | populateContacts();
78 | }
79 |
80 | function onStartScan(){
81 | waiting.running = true;
82 | }
83 |
84 | BusyIndicator {
85 | id: waiting;
86 | anchors.centerIn: parent;
87 | z: 10;
88 | running: false;
89 | }
90 |
91 | property var questionView;
92 | function onAcceptPeer(){
93 | questionView.destroy();
94 | msgManager.acceptNewMessenger();
95 | peerNickName = queuedPeerNickName;
96 | peerPortraitIndex = queuedPeerPortraitIndex;
97 | if(currentView.objectName == "chat"){
98 | currentView.changePeer(root.peerNickName, root.peerPortraitIndex);
99 | }else if(currentView.objectName == "contacts"){
100 | onAccepted();
101 | }
102 | }
103 | function onRejectPeer(){
104 | questionView.destroy();
105 | msgManager.rejectNewMessenger();
106 | }
107 |
108 | /*
109 | * a Peer want to chat with me!
110 | */
111 | function onChatRequest(name, portraitIndex, address){
112 | queuedPeerNickName = name;
113 | queuedPeerPortraitIndex = portraitIndex;
114 | var comp = Qt.createComponent("AcceptView.qml");
115 | questionView = comp.createObject(root,
116 | {
117 | "text": "[%1]%2".arg(name).arg(qsTr("want to talk with you."))
118 | });
119 | questionView.accepted.connect(onAcceptPeer);
120 | questionView.rejected.connect(onRejectPeer);
121 | }
122 |
123 | function onPeerGone(){
124 | if(currentView.objectName == "chat"){
125 | populateContacts();
126 | currentView.scanAccessPoint();
127 | }
128 | }
129 |
130 | Component.onCompleted: {
131 | var loginComp = Qt.createComponent("LoginView.qml", Component.PreferSynchronous);
132 | currentView = loginComp.createObject(root);
133 | currentView.go.connect(onLogin);
134 | msgManager.chatAccepted.connect(onAccepted);
135 | msgManager.chatRejected.connect(onRejected);
136 | msgManager.newMessenger.connect(onChatRequest);
137 | msgManager.peerGone.connect(onPeerGone);
138 | }
139 |
140 | Connections {
141 | target: apmodel;
142 | onScanFinished: waiting.running = false;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/messengerConnection.cpp:
--------------------------------------------------------------------------------
1 | #include "messengerConnection.h"
2 | #include
3 | #ifdef WIN32
4 | #include
5 | #else
6 | #include
7 | #endif
8 | #include
9 | #include "messengerManager.h"
10 | #include
11 | #include
12 | #include
13 | #include "protocol.h"
14 | #include
15 |
16 | DiscoverConnection::DiscoverConnection(QObject *parent)
17 | : QTcpSocket(parent), m_nTimeout(0)
18 | {
19 | connect(this, SIGNAL(connected()), this, SLOT(onConnected()));
20 | connect(this, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
21 | connect(this, SIGNAL(error(QAbstractSocket::SocketError)),
22 | this, SLOT(deleteLater()));
23 | }
24 |
25 | DiscoverConnection::~DiscoverConnection()
26 | {}
27 |
28 | void DiscoverConnection::connectAp(quint32 addr, quint16 port)
29 | {
30 | QHostAddress address(addr);
31 | connectToHost(address, port);
32 | m_nTimeout = startTimer(500);
33 | }
34 |
35 | void DiscoverConnection::timerEvent(QTimerEvent *e)
36 | {
37 | if(e->timerId() == m_nTimeout)
38 | {
39 | if(state() != QAbstractSocket::ConnectedState)
40 | {
41 | disconnect(this);
42 | abort();
43 | deleteLater();
44 | }
45 | killTimer(m_nTimeout);
46 | m_nTimeout = 0;
47 | return;
48 | }
49 | return QTcpSocket::timerEvent(e);
50 | }
51 |
52 | void DiscoverConnection::onConnected()
53 | {
54 | killTimer(m_nTimeout);
55 | m_nTimeout = 0;
56 | write(DISCOVER_PACKET_FULL);
57 | flush();
58 | disconnect(this, SIGNAL(error(QAbstractSocket::SocketError)),
59 | this, SLOT(deleteLater()));
60 | }
61 |
62 | void DiscoverConnection::onReadyRead()
63 | {
64 | m_data += readAll();
65 | int index = m_data.indexOf(PACKET_LINE_MARK);
66 | if(index != -1)
67 | {
68 | QString apInfo = QString::fromUtf8(m_data.left(index));
69 | QStringList infoList = apInfo.split(',');
70 | if(infoList.size() == 3 && infoList.at(0) == "discover")
71 | {
72 | AccessPoint *ap = new AccessPoint(infoList.at(1), infoList.at(2).toInt(), peerAddress());
73 | emit newAccessPoint(ap);
74 | qDebug() << "got an access point " << ap->m_address.toString() << " name " << ap->m_nickName << " portraitIndex " << ap->m_portraitIndex;
75 | }
76 |
77 | disconnect(this);
78 | close();
79 | deleteLater();
80 | }
81 | }
82 |
83 | TalkingConnection::TalkingConnection(QTcpSocket *sock, QObject *parent)
84 | : QObject(parent), m_socket(sock), m_bReady(false)
85 | , m_packetType(0), m_messageLength(0)
86 | , m_duration(0), m_peerAddress(0)
87 | {
88 | if(sock)
89 | {
90 | QHostAddress host = sock->peerAddress();
91 | m_peerAddress = host.toIPv4Address();
92 | qDebug() << "accept a connection " << host.toString();
93 | setupSignalSlots();
94 | }
95 | }
96 |
97 | TalkingConnection::~TalkingConnection()
98 | {
99 | if(m_socket)
100 | {
101 | m_socket->deleteLater();
102 | m_socket = 0;
103 | }
104 | }
105 |
106 | void TalkingConnection::accept()
107 | {
108 | if(m_socket)
109 | {
110 | replyRequest(true);
111 | m_bReady = true;
112 | m_packetType = 0;
113 | }
114 | }
115 |
116 | void TalkingConnection::reject()
117 | {
118 | if(m_socket)
119 | {
120 | if(m_socket->state() == QAbstractSocket::ConnectedState)
121 | {
122 | replyRequest(false);
123 | }
124 | closeConnection();
125 | }
126 | else
127 | {
128 | deleteLater();
129 | }
130 | }
131 |
132 | void TalkingConnection::talkTo(quint32 ip, quint16 port)
133 | {
134 | QHostAddress address(ip);
135 | m_socket = new QTcpSocket(this);
136 | setupSignalSlots();
137 | m_socket->connectToHost(address, port);
138 | m_nTimeout = startTimer(2000);
139 | m_peerAddress = ip;
140 | }
141 |
142 | void TalkingConnection::sendVoice(QByteArray &data, int duration, char *format)
143 | {
144 | QString header = QString("message,%1,%2,%3,%4\r\n")
145 | .arg(PACKET_MESSAGE_VOICE)
146 | .arg(data.length())
147 | .arg(duration).arg(format);
148 | m_socket->write(header.toUtf8());
149 | m_socket->write(data);
150 | m_socket->flush();
151 | qDebug() << "sendVoice";
152 | }
153 |
154 | QString TalkingConnection::peerAddress()
155 | {
156 | if(m_socket)
157 | {
158 | QHostAddress addr = m_socket->peerAddress();
159 | if(addr != QHostAddress::Null)
160 | {
161 | return addr.toString();
162 | }
163 | }
164 | return QString();
165 | }
166 |
167 | void TalkingConnection::onConnected()
168 | {
169 | killTimer(m_nTimeout);
170 | m_nTimeout = 0;
171 | QString strReq = QString(TALK_REQ_PACKET_MARK)
172 | .arg(MessengerManager::s_contact->m_nickName)
173 | .arg(MessengerManager::s_contact->m_portraitIndex);
174 | m_socket->write(strReq.toUtf8());
175 | m_socket->flush();
176 | }
177 |
178 | void TalkingConnection::onReadyRead()
179 | {
180 | m_data += m_socket->readAll();
181 | processPacket();
182 | }
183 |
184 | void TalkingConnection::onError(QAbstractSocket::SocketError code)
185 | {
186 | m_socket->deleteLater();
187 | m_socket = 0;
188 | emit peerGone(m_peerAddress);
189 | deleteLater();
190 | }
191 |
192 | void TalkingConnection::timerEvent(QTimerEvent *e)
193 | {
194 | if(e->timerId() == m_nTimeout)
195 | {
196 | if(m_socket->state() != QAbstractSocket::ConnectedState)
197 | {
198 | m_socket->disconnect(this);
199 | m_socket->abort();
200 | m_socket->deleteLater();
201 | m_socket = 0;
202 | deleteLater();
203 | }
204 | killTimer(m_nTimeout);
205 | m_nTimeout = 0;
206 | return;
207 | }
208 | return QObject::timerEvent(e);
209 | }
210 |
211 | void TalkingConnection::processPacket()
212 | {
213 | switch(m_packetType)
214 | {
215 | case PACKET_MESSAGE:
216 | if( processMessage() && m_data.length() ) processPacket();
217 | return;
218 | case 0:
219 | break;
220 | default:
221 | return;
222 | }
223 |
224 | int index = m_data.indexOf(PACKET_LINE_MARK);
225 | if(index == -1) return;
226 |
227 | if(m_data.startsWith(DISCOVER_PACKET))
228 | {
229 | m_packetType = PACKET_DISCOVER;
230 | discoverReply();
231 | }
232 | else if(m_data.startsWith(TALK_REQ_TOKEN))
233 | {
234 | m_packetType = PACKET_TALK_REQ;
235 | int start = m_data.indexOf(',');
236 | start++;
237 | QString peerInfo = QString::fromUtf8(m_data.mid(start, index - start));
238 | QStringList infoList = peerInfo.split(',');
239 | if(infoList.size() == 2)
240 | {
241 | Peer *peer = new Peer(infoList.at(0), infoList.at(1).toInt(), this);
242 | emit incomingMessenger(peer);
243 | }
244 | m_data = m_data.mid(index + 2);
245 | }
246 | else if(m_data.startsWith(TALK_ACCEPT_PACKET))
247 | {
248 | emit talkingAccepted();
249 | m_data = m_data.mid(index + 2);
250 | if(m_data.length()) processPacket();
251 | }
252 | else if(m_data.startsWith(TALK_REJECT_PACKET))
253 | {
254 | m_packetType = PACKET_TALK_REJECT;
255 | closeConnection();
256 | emit talkingRejected();
257 | }
258 | else if(m_data.startsWith(MESSAGE_PACKET_MARK))
259 | {
260 | QByteArray header = m_data.left(index);
261 | m_data = m_data.mid(index+2);
262 | QList headers = header.split(',');
263 | if(headers.size() != 5)
264 | {
265 | closeConnection();
266 | return;
267 | }
268 | bool ok = false;
269 | m_messageType = headers.at(1).toUInt(&ok);
270 | if(!ok || m_messageType != PACKET_MESSAGE_VOICE)
271 | {
272 | closeConnection();
273 | return;
274 | }
275 | m_messageLength = headers.at(2).toUInt(&ok);
276 | if(!ok)
277 | {
278 | closeConnection();
279 | return;
280 | }
281 | m_duration = headers.at(3).toInt(&ok);
282 | m_format = headers.at(4);
283 | m_packetType = PACKET_MESSAGE;
284 | if( processMessage() && m_data.length() ) processPacket();
285 | }
286 | }
287 |
288 |
289 | void TalkingConnection::replyRequest(bool agree)
290 | {
291 | const char *packet = agree ? TALK_ACCEPT_PACKET_FULL : TALK_REJECT_PACKET_FULL;
292 | m_socket->write(packet);
293 | m_socket->flush();
294 | }
295 |
296 | void TalkingConnection::discoverReply()
297 | {
298 | QString strReply = QString(DISCOVER_REPLY_MARK)
299 | .arg(MessengerManager::s_contact->m_nickName)
300 | .arg(MessengerManager::s_contact->m_portraitIndex);
301 | m_socket->write(strReply.toUtf8());
302 | //qDebug() << "after write name to client";
303 | m_socket->flush();
304 | closeConnection();
305 | //qDebug() << "TalkingConnection, discoverReply";
306 | }
307 |
308 | void TalkingConnection::closeConnection()
309 | {
310 | m_socket->disconnect(this);
311 | m_socket->close();
312 | m_socket->deleteLater();
313 | m_socket = 0;
314 | deleteLater();
315 | }
316 |
317 | void TalkingConnection::setupSignalSlots()
318 | {
319 | connect(m_socket, SIGNAL(connected()),this, SLOT(onConnected()));
320 | connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
321 | connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)),
322 | this, SLOT(onError(QAbstractSocket::SocketError)));
323 | }
324 |
325 | int TalkingConnection::processMessage()
326 | {
327 | if(m_data.length() < m_messageLength) return 0;
328 | QString fileName = QString("%1/%2.%3")
329 | .arg(QDir::currentPath())
330 | .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss"))
331 | .arg(m_format.data());
332 | QFile file(fileName);
333 | if(file.open(QFile::WriteOnly))
334 | {
335 | file.write(m_data.data(), m_messageLength);
336 | file.close();
337 | emit incomingVoice(fileName, m_duration);
338 | }
339 |
340 | if(m_data.length() > m_messageLength)
341 | {
342 | m_data = m_data.mid(m_messageLength);
343 | }
344 | else
345 | {
346 | m_data.clear();
347 | }
348 | m_packetType = 0;
349 | m_messageLength = 0;
350 | m_duration = 0;
351 | m_format.clear();
352 | return 1;
353 | }
354 |
--------------------------------------------------------------------------------
/messengerConnection.h:
--------------------------------------------------------------------------------
1 | #ifndef ServerConnection_H
2 | #define ServerConnection_H
3 | #include
4 | #include
5 | #include "protocol.h"
6 | #include
7 | #include
8 | #include
9 |
10 | class Contact;
11 | class Peer;
12 | class AccessPoint;
13 |
14 | class DiscoverConnection : public QTcpSocket
15 | {
16 | Q_OBJECT
17 | public:
18 | DiscoverConnection(QObject *parent = 0);
19 | ~DiscoverConnection();
20 |
21 | void connectAp(quint32 addr, quint16 port);
22 |
23 | signals:
24 | void newAccessPoint(AccessPoint *ap);
25 |
26 | protected:
27 | void timerEvent(QTimerEvent *e);
28 |
29 | protected slots:
30 | void onConnected();
31 | void onReadyRead();
32 |
33 | private:
34 | int m_nTimeout;
35 | QByteArray m_data;
36 | };
37 |
38 | class TalkingConnection : public QObject
39 | {
40 | Q_OBJECT
41 | public:
42 | TalkingConnection(QTcpSocket *sock = 0, QObject *parent = 0);
43 | ~TalkingConnection();
44 |
45 | void accept();
46 | void reject();
47 | void talkTo(quint32 ip, quint16 port);
48 | void sendVoice(QByteArray &data, int duration, char *format);
49 | QString peerAddress();
50 |
51 | signals:
52 | void talkingAccepted();
53 | void talkingRejected();
54 | void incomingMessenger(Peer *peer);
55 | void incomingVoice(QString fileName, int duration);
56 | void peerGone(quint32 peerAddr); //error or peer quit app
57 |
58 | protected slots:
59 | void onConnected();
60 | void onReadyRead();
61 | void onError(QAbstractSocket::SocketError code);
62 |
63 | protected:
64 | void timerEvent(QTimerEvent *e);
65 |
66 | private:
67 | void processPacket();
68 | void replyRequest(bool agree);
69 | void discoverReply();
70 | int processMessage();
71 | void closeConnection();
72 | void setupSignalSlots();
73 |
74 | private:
75 | QTcpSocket *m_socket;
76 | bool m_bReady;
77 | int m_nTimeout;
78 | int m_packetType;
79 | quint32 m_messageType;
80 | quint32 m_messageLength;
81 | QByteArray m_data;
82 | qint32 m_duration;
83 | QByteArray m_format;
84 | quint32 m_peerAddress;
85 | };
86 |
87 | class Contact
88 | {
89 | public:
90 | Contact(): m_portraitIndex(0)
91 | {
92 | }
93 | Contact(const QString &nickName, int portraitIndex)
94 | : m_nickName(nickName), m_portraitIndex(portraitIndex)
95 | {}
96 |
97 | virtual ~Contact(){}
98 |
99 | QString m_nickName;
100 | int m_portraitIndex;
101 | };
102 |
103 | class AccessPoint : public Contact
104 | {
105 | public:
106 | AccessPoint(const QString &nickName, int portraitIndex, const QHostAddress &addr)
107 | : Contact(nickName, portraitIndex)
108 | ,m_address(addr)
109 | {
110 | }
111 | ~AccessPoint()
112 | {
113 | }
114 |
115 | QHostAddress m_address;
116 | };
117 |
118 | class Peer : public Contact
119 | {
120 | public:
121 | Peer(const QString &nickName, int portraitIndex, TalkingConnection *conn)
122 | : Contact(nickName, portraitIndex)
123 | ,m_connection(conn)
124 | {
125 |
126 | }
127 | ~Peer()
128 | {
129 | if(m_connection)m_connection->reject();
130 | }
131 |
132 | QPointer m_connection;
133 | };
134 |
135 | #endif // ServerConnection_H
136 |
--------------------------------------------------------------------------------
/messengerManager.cpp:
--------------------------------------------------------------------------------
1 | #include "messengerManager.h"
2 | #include
3 | #include
4 |
5 | Contact *MessengerManager::s_contact = 0;
6 | MessengerManager::MessengerManager(QObject *parent)
7 | : QTcpServer(parent), m_currentPeer(0), m_pendingPeer(0)
8 | {
9 | if(!s_contact)
10 | {
11 | s_contact = new Contact();
12 | }
13 | connect(this, SIGNAL(newConnection()),
14 | this, SLOT(onNewConnection()));
15 | }
16 |
17 | MessengerManager::~MessengerManager()
18 | {
19 |
20 | }
21 |
22 | void MessengerManager::acceptNewMessenger()
23 | {
24 | if(m_currentPeer)
25 | {
26 | delete m_currentPeer;
27 | }
28 | m_currentPeer = m_pendingPeer;
29 | m_currentPeer->m_connection->accept();
30 | m_pendingPeer = 0;
31 | setupSignalSlots(m_currentPeer->m_connection);
32 | }
33 |
34 | void MessengerManager::rejectNewMessenger()
35 | {
36 | if(m_pendingPeer)
37 | {
38 | delete m_pendingPeer;
39 | m_pendingPeer = 0;
40 | }
41 | }
42 |
43 | void MessengerManager::closeCurrentSession()
44 | {
45 | if(m_currentPeer)
46 | {
47 | delete m_currentPeer;
48 | m_currentPeer = 0;
49 | }
50 | }
51 |
52 | void MessengerManager::start(QString nickName, int portraitIndex)
53 | {
54 | s_contact->m_nickName = nickName;
55 | s_contact->m_portraitIndex = portraitIndex;
56 | listen(QHostAddress::AnyIPv4, SERVER_PORT);
57 | }
58 |
59 | void MessengerManager::talkTo(AccessPoint *ap)
60 | {
61 | rejectNewMessenger();
62 | closeCurrentSession();
63 |
64 | TalkingConnection *conn = new TalkingConnection();
65 | m_currentPeer = new Peer(ap->m_nickName, ap->m_portraitIndex, conn);
66 | conn->talkTo(ap->m_address.toIPv4Address(), SERVER_PORT);
67 | setupSignalSlots(conn);
68 | }
69 |
70 | MessengerManager *MessengerManager::instance()
71 | {
72 | static MessengerManager *server = new MessengerManager();
73 | return server;
74 | }
75 |
76 | void MessengerManager::sendVoiceMessage(QString fileName, qint64 duration)
77 | {
78 | if(m_currentPeer)
79 | {
80 | QFile file(fileName);
81 | if( file.open(QFile::ReadOnly) )
82 | {
83 | int sep = fileName.lastIndexOf('.');
84 | QString format = fileName.mid(sep+1);
85 | QByteArray data = file.readAll();
86 | file.close();
87 | m_currentPeer->m_connection->sendVoice(data, duration, format.toLatin1().data());
88 | }
89 | else
90 | {
91 | qDebug() << "could not open - " << fileName << " error - " << file.errorString();
92 | }
93 | }
94 | }
95 |
96 | void MessengerManager::onNewConnection()
97 | {
98 | while(hasPendingConnections())
99 | {
100 | TalkingConnection *m = new TalkingConnection(nextPendingConnection());
101 | connect(m, SIGNAL(incomingMessenger(Peer*)),
102 | this, SLOT(onIncomingMessenger(Peer*)));
103 | }
104 | }
105 |
106 | void MessengerManager::onIncomingMessenger(Peer *peer)
107 | {
108 | if(m_pendingPeer)
109 | {
110 | //sorry...we just allow one pending peer
111 | delete peer;
112 | }
113 | else
114 | {
115 | m_pendingPeer = peer;
116 | emit newMessenger(peer->m_nickName,
117 | peer->m_portraitIndex,
118 | peer->m_connection->peerAddress());
119 | }
120 | }
121 |
122 | void MessengerManager::setupSignalSlots(TalkingConnection *conn)
123 | {
124 | connect(conn, SIGNAL(talkingAccepted()), this, SIGNAL(chatAccepted()));
125 | connect(conn, SIGNAL(talkingRejected()), this, SIGNAL(chatRejected()));
126 | connect(conn, SIGNAL(incomingVoice(QString,int)), this, SIGNAL(voiceMessageArrived(QString,int)));
127 | connect(conn, SIGNAL(peerGone(quint32)), this, SIGNAL(peerGone()));
128 | }
129 |
--------------------------------------------------------------------------------
/messengerManager.h:
--------------------------------------------------------------------------------
1 | #ifndef MESSENGER_MANAGER_H
2 | #define MESSENGER_MANAGER_H
3 | #include
4 | #include "messengerConnection.h"
5 |
6 | class MessengerManager : public QTcpServer
7 | {
8 | Q_OBJECT
9 | MessengerManager(QObject *parent = 0);
10 | public:
11 | ~MessengerManager();
12 |
13 | Q_INVOKABLE void acceptNewMessenger();
14 | Q_INVOKABLE void rejectNewMessenger();
15 | Q_INVOKABLE void closeCurrentSession();
16 |
17 | static Contact * s_contact;
18 | Q_INVOKABLE void start(QString nickName, int portraitIndex);
19 |
20 | void talkTo(AccessPoint *ap);
21 |
22 | static MessengerManager *instance();
23 | Q_INVOKABLE void sendVoiceMessage(QString fileName, qint64 duration);
24 |
25 | signals:
26 | void newMessenger(QString name, int portraitIndex, QString address);
27 | void chatAccepted();
28 | void chatRejected();
29 | void voiceMessageArrived(QString fileName, int duration);
30 | void peerGone();
31 |
32 | protected slots:
33 | void onNewConnection();
34 | void onIncomingMessenger(Peer *peer);
35 |
36 | private:
37 | void setupSignalSlots(TalkingConnection *conn);
38 |
39 | private:
40 | Peer * m_currentPeer;
41 | Peer * m_pendingPeer;
42 | };
43 |
44 | #endif // MessengerManager_H
45 |
--------------------------------------------------------------------------------
/pointSizeToPixelSize.cpp:
--------------------------------------------------------------------------------
1 | #include "pointSizeToPixelSize.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | int PointSizeToPixelSize::pixelSize(int pointSize)
9 | {
10 | QScreen *screen = qApp->primaryScreen();
11 | int ps = (pointSize * screen->logicalDotsPerInch()) / 72;
12 | qDebug() << "pointSize " << pointSize << " - pixelSize " << ps;
13 | return ps;
14 | }
15 |
16 | int PointSizeToPixelSize::height(int pointSize)
17 | {
18 | QFont font = qApp->font();
19 | font.setPointSize(pointSize);
20 | QFontMetrics fm(font);
21 | return fm.height();
22 | }
23 |
24 | int PointSizeToPixelSize::width(int pointSize, QString text)
25 | {
26 | QFont font = qApp->font();
27 | font.setPointSize(pointSize);
28 | QFontMetrics fm(font);
29 | return fm.width(text);
30 | }
31 |
--------------------------------------------------------------------------------
/pointSizeToPixelSize.h:
--------------------------------------------------------------------------------
1 | #ifndef POINTSIZETOPIXELSIZE_H
2 | #define POINTSIZETOPIXELSIZE_H
3 | #include
4 |
5 | class PointSizeToPixelSize : public QObject
6 | {
7 | Q_OBJECT
8 | public:
9 | PointSizeToPixelSize(QObject *parent = 0){}
10 | ~PointSizeToPixelSize(){}
11 |
12 | Q_INVOKABLE int pixelSize(int pointSize);
13 | Q_INVOKABLE int height(int pointSize);
14 | Q_INVOKABLE int width(int pointSize, QString text);
15 | };
16 |
17 | #endif // POINTSIZETOPIXELSIZE_H
18 |
--------------------------------------------------------------------------------
/protocol.h:
--------------------------------------------------------------------------------
1 | #ifndef PROTOCOL_H
2 | #define PROTOCOL_H
3 |
4 | #define SERVER_PORT 5661
5 | #define PACKET_LINE_MARK "\r\n"
6 | #define DISCOVER_PACKET "discover"
7 | #define DISCOVER_PACKET_FULL "discover\r\n"
8 | #define DISCOVER_REPLY_FORMAT "discover,${name},${portraitIndex}\r\n"
9 | #define DISCOVER_REPLY_MARK "discover,%1,%2\r\n"
10 | #define TALK_REQ_PACKET_FORMAT "talking,${name},${portraitIndex}\r\n"
11 | #define TALK_REQ_PACKET_MARK "talking,%1,%2\r\n"
12 | #define TALK_REQ_TOKEN "talking"
13 | #define TALK_REJECT_PACKET "reject"
14 | #define TALK_REJECT_PACKET_FULL "reject\r\n"
15 | #define TALK_ACCEPT_PACKET "accept"
16 | #define TALK_ACCEPT_PACKET_FULL "accept\r\n"
17 |
18 | #define MESSAGE_PACKET_MARK "message"
19 | #define MESSAGE_PACKET_FORMAT "message,${type},${lengthOfBody},${duration},${format}\r\n${BODY DATA}"
20 |
21 | #define PACKET_DISCOVER 1
22 | #define PACKET_DISCOVER_REPLY 2
23 | #define PACKET_TALK_REQ 3
24 | #define PACKET_TALK_ACCEPT 4
25 | #define PACKET_TALK_REJECT 5
26 | #define PACKET_MESSAGE 6
27 | #define PACKET_MESSAGE_TEXT 1
28 | #define PACKET_MESSAGE_VOICE 2
29 |
30 | #endif // PROTOCOL_H
31 |
--------------------------------------------------------------------------------
/qml.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | res/head_0.png
4 | res/head_1.png
5 | res/head_2.png
6 | res/head_3.png
7 | res/head_4.png
8 | res/head_5.png
9 | res/head_6.png
10 | res/head_7.png
11 | res/head_8.png
12 | res/head_9.png
13 | res/ic_ok.png
14 | res/ic_ok_pressed.png
15 | LoginView.qml
16 | main.qml
17 | ImageButton.qml
18 | ChatView.qml
19 | Contacts.qml
20 | res/ic_microphone.png
21 | res/chat_from_bg_normal.9.png
22 | res/chat_from_bg_pressed.9.png
23 | res/chat_to_bg_normal.9.png
24 | res/chat_to_bg_pressed.9.png
25 | res/ic_back.png
26 | res/ic_back_pressed.png
27 | res/ic_voice.png
28 | res/ic_search.png
29 | res/ic_search_pressed.png
30 | res/ic_chat.png
31 | res/ic_chat_pressed.png
32 | AcceptView.qml
33 | res/ic_accept.png
34 | res/ic_reject.png
35 | LVChat.qm
36 | FlatButton.qml
37 | res/appicon.png
38 |
39 |
40 |
--------------------------------------------------------------------------------
/res/appicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/appicon.png
--------------------------------------------------------------------------------
/res/chat_from_bg_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/chat_from_bg_normal.9.png
--------------------------------------------------------------------------------
/res/chat_from_bg_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/chat_from_bg_pressed.9.png
--------------------------------------------------------------------------------
/res/chat_to_bg_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/chat_to_bg_normal.9.png
--------------------------------------------------------------------------------
/res/chat_to_bg_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/chat_to_bg_pressed.9.png
--------------------------------------------------------------------------------
/res/head_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_0.png
--------------------------------------------------------------------------------
/res/head_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_1.png
--------------------------------------------------------------------------------
/res/head_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_2.png
--------------------------------------------------------------------------------
/res/head_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_3.png
--------------------------------------------------------------------------------
/res/head_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_4.png
--------------------------------------------------------------------------------
/res/head_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_5.png
--------------------------------------------------------------------------------
/res/head_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_6.png
--------------------------------------------------------------------------------
/res/head_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_7.png
--------------------------------------------------------------------------------
/res/head_8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_8.png
--------------------------------------------------------------------------------
/res/head_9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/head_9.png
--------------------------------------------------------------------------------
/res/ic_accept.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_accept.png
--------------------------------------------------------------------------------
/res/ic_back - 副本.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_back - 副本.png
--------------------------------------------------------------------------------
/res/ic_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_back.png
--------------------------------------------------------------------------------
/res/ic_back_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_back_pressed.png
--------------------------------------------------------------------------------
/res/ic_chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_chat.png
--------------------------------------------------------------------------------
/res/ic_chat_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_chat_pressed.png
--------------------------------------------------------------------------------
/res/ic_microphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_microphone.png
--------------------------------------------------------------------------------
/res/ic_ok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_ok.png
--------------------------------------------------------------------------------
/res/ic_ok_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_ok_pressed.png
--------------------------------------------------------------------------------
/res/ic_reject.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_reject.png
--------------------------------------------------------------------------------
/res/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_search.png
--------------------------------------------------------------------------------
/res/ic_search_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_search_pressed.png
--------------------------------------------------------------------------------
/res/ic_voice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/ic_voice.png
--------------------------------------------------------------------------------
/res/liao.tsp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/res/liao.tsp
--------------------------------------------------------------------------------
/scaleableBorder.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 |
3 | Rectangle {
4 | width: 450;
5 | height: 300;
6 |
7 | Row{
8 | anchors.centerIn: parent;
9 | BorderImage{
10 | width: 130;
11 | height: 200;
12 | border.left: 16;
13 | border.top: 38;
14 | border.right: 6;
15 | border.bottom: 6;
16 | source: "res/chat_from_bg_normal.9.png";
17 | }
18 | BorderImage{
19 | width: 200;
20 | height: 150;
21 | border.left: 16;
22 | border.top: 38;
23 | border.right: 6;
24 | border.bottom: 6;
25 | source: "res/chat_from_bg_normal.9.png";
26 | }
27 | BorderImage{
28 | width: 100;
29 | height: 280;
30 | border.left: 16;
31 | border.top: 38;
32 | border.right: 6;
33 | border.bottom: 6;
34 | source: "res/chat_from_bg_normal.9.png";
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/scanner.cpp:
--------------------------------------------------------------------------------
1 | #include "scanner.h"
2 | #include
3 | #include
4 | #include
5 | #include "messengerConnection.h"
6 | #include
7 |
8 | AccessPointScanner::AccessPointScanner(QObject *parent)
9 | : QObject(parent)
10 | ,m_index(0), m_finished(0)
11 | {
12 | initializeScanList();
13 | }
14 |
15 | AccessPointScanner::~AccessPointScanner()
16 | {
17 |
18 | }
19 |
20 | void AccessPointScanner::startScan()
21 | {
22 | if(m_scanList.size() > 0)
23 | {
24 | scanAps();
25 | }
26 | else
27 | {
28 | qDebug() << "none scan list";
29 | emit scanFinished();
30 | }
31 | }
32 |
33 | void AccessPointScanner::onDiscoverSocketDestroyed()
34 | {
35 | m_finished++;
36 | if(++m_index < m_scanList.size())
37 | {
38 | scanOneAp();
39 | return;
40 | }
41 | if(m_finished == m_scanList.size())
42 | {
43 | m_index = 0;
44 | m_finished = 0;
45 | emit scanFinished();
46 | qDebug() << "emit scanFinished";
47 | }
48 | }
49 |
50 | void AccessPointScanner::scanOneAp()
51 | {
52 | DiscoverConnection *conn = new DiscoverConnection();
53 | connect(conn, SIGNAL(newAccessPoint(AccessPoint *)),
54 | this, SIGNAL(newAccessPoint(AccessPoint *)));
55 | connect(conn, SIGNAL(destroyed()), this, SLOT(onDiscoverSocketDestroyed()));
56 | conn->connectAp(m_scanList.at(m_index), SERVER_PORT);
57 | //qDebug() << "scaning " << QHostAddress(m_scanList.at(m_index));
58 | }
59 |
60 | void AccessPointScanner::scanAps()
61 | {
62 | int limit = qMin(m_scanList.size(), 10);
63 | for(; m_index < limit; ++m_index)
64 | {
65 | scanOneAp();
66 | }
67 | --m_index;
68 | }
69 |
70 | void AccessPointScanner::initializeScanList()
71 | {
72 | QList interfaces = QNetworkInterface::allInterfaces();
73 | foreach(QNetworkInterface interface, interfaces)
74 | {
75 | int flags = interface.flags();
76 | if(flags & QNetworkInterface::IsLoopBack) continue;
77 | if(!(flags &(QNetworkInterface::IsUp | QNetworkInterface::IsRunning))) continue;
78 |
79 | QList entries = interface.addressEntries();
80 | int count = entries.size();
81 | for(int i = 0; i < count; i++)
82 | {
83 | const QNetworkAddressEntry &entry = entries.at(i);
84 | QHostAddress ip = entry.ip();
85 | if(!ip.isLoopback() && ip.protocol() == QAbstractSocket::IPv4Protocol)
86 | {
87 | quint32 myself = ip.toIPv4Address();
88 | int numberOfIps = ipCount(entry.prefixLength());
89 | quint32 startIp = (ip.toIPv4Address() & entry.netmask().toIPv4Address())+1;
90 | for(int j = 1; j < numberOfIps; j++, startIp++)
91 | {
92 | if(myself != startIp)
93 | {
94 | m_scanList.append(startIp);
95 | //QHostAddress address(startIp);
96 | //qDebug() << address;
97 | }
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
104 | int AccessPointScanner::ipCount(int prefixLength)
105 | {
106 | int count = 0;
107 | int unmask = 32 - prefixLength;
108 | for(int i = 0; i < unmask; i++)
109 | {
110 | count += pow(2, i);
111 | }
112 | return count;
113 | }
114 |
--------------------------------------------------------------------------------
/scanner.h:
--------------------------------------------------------------------------------
1 | #ifndef SCANNER_H
2 | #define SCANNER_H
3 | #include
4 | #include
5 | #include
6 |
7 | class AccessPoint;
8 | class AccessPointScanner : public QObject
9 | {
10 | Q_OBJECT
11 | public:
12 | AccessPointScanner(QObject *parent = 0);
13 | ~AccessPointScanner();
14 |
15 | void startScan();
16 |
17 | signals:
18 | void newAccessPoint(AccessPoint *ap);
19 | void scanFinished();
20 |
21 | protected slots:
22 | void onDiscoverSocketDestroyed();
23 |
24 | private:
25 | void scanAps();
26 | void initializeScanList();
27 | int ipCount(int prefixLength);
28 | bool compareAccessPoints();
29 | void scanOneAp();
30 |
31 | private:
32 | QList m_scanList;
33 | int m_index;
34 | int m_finished;
35 | };
36 |
37 | #endif // SCANNER_H
38 |
--------------------------------------------------------------------------------
/screenshots/pc_init.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/pc_init.png
--------------------------------------------------------------------------------
/screenshots/pc_select_head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/pc_select_head.png
--------------------------------------------------------------------------------
/screenshots/scaleableBorder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/scaleableBorder.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923214402.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923214402.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923214424.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923214424.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923214450.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923214450.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923214619.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923214619.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923214651.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923214651.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923214816.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923214816.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923214838.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923214838.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923214854.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923214854.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923222600.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923222600.png
--------------------------------------------------------------------------------
/screenshots/豌豆荚截图20140923222705.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/foruok/LVChat/40412e8683220e07f69d298ac71fc9aa91f36304/screenshots/豌豆荚截图20140923222705.png
--------------------------------------------------------------------------------
/voiceMessage.h:
--------------------------------------------------------------------------------
1 | #ifndef VOICEMESSAGE_H
2 | #define VOICEMESSAGE_H
3 | #include
4 | #include
5 | class VoiceMessage
6 | {
7 | public:
8 | VoiceMessage(const QString &fileName, int duration)
9 | : m_fileName(fileName), m_duration(duration)
10 | , m_time(QDateTime::currentDateTime())
11 | {
12 | }
13 |
14 | QString m_fileName;
15 | int m_duration;
16 | QDateTime m_time;
17 | };
18 |
19 | #endif // VOICEMESSAGE_H
20 |
--------------------------------------------------------------------------------