├── README.md
├── qtc_packaging
└── debian_harmattan
│ ├── compat
│ ├── changelog
│ ├── README
│ ├── copyright
│ ├── rules
│ └── control
├── img
├── close.png
├── delete.png
├── menu.png
├── send.png
├── loading.png
├── refresh.png
├── arrow-left.png
├── attachment.png
├── exit-to-app.png
├── filters
│ ├── all.png
│ ├── cat.png
│ ├── book.png
│ ├── bots.png
│ ├── crown.png
│ ├── edit.png
│ ├── game.png
│ ├── home.png
│ ├── light.png
│ ├── like.png
│ ├── love.png
│ ├── mask.png
│ ├── money.png
│ ├── note.png
│ ├── party.png
│ ├── sport.png
│ ├── study.png
│ ├── trade.png
│ ├── work.png
│ ├── airplane.png
│ ├── channels.png
│ ├── custom.png
│ ├── favorite.png
│ ├── flower.png
│ ├── groups.png
│ ├── palette.png
│ ├── private.png
│ ├── travel.png
│ ├── unmuted.png
│ └── unread.png
├── fullscreen.png
├── media
│ ├── file.png
│ ├── image.png
│ ├── poll.png
│ ├── web.png
│ ├── account.png
│ ├── download.png
│ ├── map-marker.png
│ ├── receipt-text.png
│ ├── dice-multiple.png
│ ├── gamepad-square.png
│ └── close-circle-outline_inner.png
├── send_accent.png
├── dots-vertical.png
├── loading_white.png
├── arrow-left_black.png
├── share_forwarded.png
├── magnify-minus-outline.png
├── magnify-plus-outline.png
├── checkbox-marked-circle.png
├── close-circle-outline_inner.png
└── checkbox-blank-circle-outline.png
├── kutegramquick.rc
├── kutegramquick.icns
├── kutegramquick.ico
├── kutegramquick.png
├── kutegramquick_big.png
├── kutegramquick_pigler.png
├── kutegramquick_small.png
├── wpassets
├── logo_44x44.png
├── logo_71x71.png
├── logo_store.png
├── logo_150x150.png
├── logo_310x150.png
└── logo_480x800.png
├── android
├── res
│ ├── drawable-hdpi
│ │ └── icon.png
│ ├── drawable-ldpi
│ │ └── icon.png
│ ├── drawable-mdpi
│ │ └── icon.png
│ └── values
│ │ └── libs.xml
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── build.gradle
├── gradlew.bat
├── AndroidManifest.xml
└── gradlew
├── .gitmodules
├── qml
├── control
│ ├── MessageIntroPage.qml
│ ├── Spinner.qml
│ ├── DrawerButton.qml
│ ├── LineEdit.qml
│ ├── SnackBar.qml
│ ├── MainScreen.qml
│ ├── AuthScreen.qml
│ ├── ImageViewer.qml
│ ├── Drawer.qml
│ └── TopBar.qml
├── dialog
│ ├── FolderItem.qml
│ ├── DialogPage.qml
│ └── DialogItem.qml
├── auth
│ ├── Button.qml
│ ├── CodePage.qml
│ ├── IntroPage.qml
│ └── PhonePage.qml
├── message
│ ├── MessagePage.qml
│ ├── MessageImage.qml
│ ├── MessageItem.qml
│ ├── MessageDocument.qml
│ └── MessageEdit.qml
└── main.qml
├── .gitignore
├── kutegramquick.desktop
├── src
├── messageutil.h
├── currentuserinfo.h
├── avatardownloader.h
├── foldersmodel.h
├── platformutils.h
├── dialogsmodel.h
├── currentuserinfo.cpp
├── messagesmodel.h
├── main.cpp
├── avatardownloader.cpp
├── platformutils.cpp
└── foldersmodel.cpp
├── qmlapplicationviewer
├── qmlapplicationviewer.h
├── qmlapplicationviewer.cpp
└── qmlapplicationviewer.pri
├── kutegramquick.svg
├── resources.qrc
└── kutegramquick.pro
/README.md:
--------------------------------------------------------------------------------
1 | # Kutegram
--------------------------------------------------------------------------------
/qtc_packaging/debian_harmattan/compat:
--------------------------------------------------------------------------------
1 | 7
2 |
--------------------------------------------------------------------------------
/img/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/close.png
--------------------------------------------------------------------------------
/img/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/delete.png
--------------------------------------------------------------------------------
/img/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/menu.png
--------------------------------------------------------------------------------
/img/send.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/send.png
--------------------------------------------------------------------------------
/img/loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/loading.png
--------------------------------------------------------------------------------
/img/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/refresh.png
--------------------------------------------------------------------------------
/kutegramquick.rc:
--------------------------------------------------------------------------------
1 | IDI_ICON1 ICON DISCARDABLE "kutegramquick.ico"
--------------------------------------------------------------------------------
/img/arrow-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/arrow-left.png
--------------------------------------------------------------------------------
/img/attachment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/attachment.png
--------------------------------------------------------------------------------
/img/exit-to-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/exit-to-app.png
--------------------------------------------------------------------------------
/img/filters/all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/all.png
--------------------------------------------------------------------------------
/img/filters/cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/cat.png
--------------------------------------------------------------------------------
/img/fullscreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/fullscreen.png
--------------------------------------------------------------------------------
/img/media/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/file.png
--------------------------------------------------------------------------------
/img/media/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/image.png
--------------------------------------------------------------------------------
/img/media/poll.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/poll.png
--------------------------------------------------------------------------------
/img/media/web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/web.png
--------------------------------------------------------------------------------
/img/send_accent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/send_accent.png
--------------------------------------------------------------------------------
/kutegramquick.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/kutegramquick.icns
--------------------------------------------------------------------------------
/kutegramquick.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/kutegramquick.ico
--------------------------------------------------------------------------------
/kutegramquick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/kutegramquick.png
--------------------------------------------------------------------------------
/img/dots-vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/dots-vertical.png
--------------------------------------------------------------------------------
/img/filters/book.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/book.png
--------------------------------------------------------------------------------
/img/filters/bots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/bots.png
--------------------------------------------------------------------------------
/img/filters/crown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/crown.png
--------------------------------------------------------------------------------
/img/filters/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/edit.png
--------------------------------------------------------------------------------
/img/filters/game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/game.png
--------------------------------------------------------------------------------
/img/filters/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/home.png
--------------------------------------------------------------------------------
/img/filters/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/light.png
--------------------------------------------------------------------------------
/img/filters/like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/like.png
--------------------------------------------------------------------------------
/img/filters/love.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/love.png
--------------------------------------------------------------------------------
/img/filters/mask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/mask.png
--------------------------------------------------------------------------------
/img/filters/money.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/money.png
--------------------------------------------------------------------------------
/img/filters/note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/note.png
--------------------------------------------------------------------------------
/img/filters/party.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/party.png
--------------------------------------------------------------------------------
/img/filters/sport.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/sport.png
--------------------------------------------------------------------------------
/img/filters/study.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/study.png
--------------------------------------------------------------------------------
/img/filters/trade.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/trade.png
--------------------------------------------------------------------------------
/img/filters/work.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/work.png
--------------------------------------------------------------------------------
/img/loading_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/loading_white.png
--------------------------------------------------------------------------------
/img/media/account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/account.png
--------------------------------------------------------------------------------
/kutegramquick_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/kutegramquick_big.png
--------------------------------------------------------------------------------
/img/arrow-left_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/arrow-left_black.png
--------------------------------------------------------------------------------
/img/filters/airplane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/airplane.png
--------------------------------------------------------------------------------
/img/filters/channels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/channels.png
--------------------------------------------------------------------------------
/img/filters/custom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/custom.png
--------------------------------------------------------------------------------
/img/filters/favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/favorite.png
--------------------------------------------------------------------------------
/img/filters/flower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/flower.png
--------------------------------------------------------------------------------
/img/filters/groups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/groups.png
--------------------------------------------------------------------------------
/img/filters/palette.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/palette.png
--------------------------------------------------------------------------------
/img/filters/private.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/private.png
--------------------------------------------------------------------------------
/img/filters/travel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/travel.png
--------------------------------------------------------------------------------
/img/filters/unmuted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/unmuted.png
--------------------------------------------------------------------------------
/img/filters/unread.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/filters/unread.png
--------------------------------------------------------------------------------
/img/media/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/download.png
--------------------------------------------------------------------------------
/img/media/map-marker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/map-marker.png
--------------------------------------------------------------------------------
/img/share_forwarded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/share_forwarded.png
--------------------------------------------------------------------------------
/kutegramquick_pigler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/kutegramquick_pigler.png
--------------------------------------------------------------------------------
/kutegramquick_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/kutegramquick_small.png
--------------------------------------------------------------------------------
/wpassets/logo_44x44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/wpassets/logo_44x44.png
--------------------------------------------------------------------------------
/wpassets/logo_71x71.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/wpassets/logo_71x71.png
--------------------------------------------------------------------------------
/wpassets/logo_store.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/wpassets/logo_store.png
--------------------------------------------------------------------------------
/img/media/receipt-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/receipt-text.png
--------------------------------------------------------------------------------
/wpassets/logo_150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/wpassets/logo_150x150.png
--------------------------------------------------------------------------------
/wpassets/logo_310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/wpassets/logo_310x150.png
--------------------------------------------------------------------------------
/wpassets/logo_480x800.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/wpassets/logo_480x800.png
--------------------------------------------------------------------------------
/img/magnify-minus-outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/magnify-minus-outline.png
--------------------------------------------------------------------------------
/img/magnify-plus-outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/magnify-plus-outline.png
--------------------------------------------------------------------------------
/img/media/dice-multiple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/dice-multiple.png
--------------------------------------------------------------------------------
/img/media/gamepad-square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/gamepad-square.png
--------------------------------------------------------------------------------
/img/checkbox-marked-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/checkbox-marked-circle.png
--------------------------------------------------------------------------------
/android/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/android/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/android/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/android/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/android/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/android/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/img/close-circle-outline_inner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/close-circle-outline_inner.png
--------------------------------------------------------------------------------
/img/checkbox-blank-circle-outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/checkbox-blank-circle-outline.png
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/img/media/close-circle-outline_inner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kutegram/quick/HEAD/img/media/close-circle-outline_inner.png
--------------------------------------------------------------------------------
/qtc_packaging/debian_harmattan/changelog:
--------------------------------------------------------------------------------
1 | kutegramquick (0.0.1) unstable; urgency=low
2 |
3 | * Initial Release.
4 |
5 | -- unknown <> Wed, 09 Aug 2023 11:02:57 +0300
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libkg"]
2 | path = libkg
3 | url = https://github.com/kutegram/libkg
4 | [submodule "pigler"]
5 | path = pigler
6 | url = https://github.com/piglerorg/pigler.git
7 |
--------------------------------------------------------------------------------
/qtc_packaging/debian_harmattan/README:
--------------------------------------------------------------------------------
1 | The Debian Package kutegramquick
2 | ----------------------------
3 |
4 | Comments regarding the Package
5 |
6 | -- unknown <> Wed, 09 Aug 2023 11:02:57 +0300
7 |
--------------------------------------------------------------------------------
/qml/control/MessageIntroPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Rectangle {
4 | color: "#FFFFFF"
5 |
6 | Text {
7 | anchors.centerIn: parent
8 | text: "Select a chat to start messaging"
9 | font.pixelSize: 12 * kgScaling
10 | }
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.user
2 | moc
3 | bld.inf
4 | *.loc
5 | *.rss
6 | *.sis
7 | *.mmp
8 | *.pkg
9 | Makefile
10 | *.cache
11 | *.mk
12 | ABLD.BAT
13 | *test
14 | rcc/qrc_resources.cpp
15 | *.pro.user.*
16 | *.autosave
17 | debian
18 | obj
19 | build-stamp
20 | configure-stamp
21 | Kutegram
22 | *.deb
23 | *.changes
24 |
--------------------------------------------------------------------------------
/kutegramquick.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Encoding=UTF-8
3 | Version=1.0
4 | Type=Application
5 | Terminal=false
6 | Name=kutegram-quick
7 | Exec=/opt/kutegram-quick/bin/kutegram-quick
8 | Icon=kutegram-quick
9 | X-Window-Icon=
10 | X-HildonDesk-ShowInToolbar=true
11 | X-Osso-Type=application/x-executable
12 |
--------------------------------------------------------------------------------
/src/messageutil.h:
--------------------------------------------------------------------------------
1 | #ifndef MESSAGEUTIL_H
2 | #define MESSAGEUTIL_H
3 |
4 | #include "tgstream.h"
5 |
6 | TgList& globalUsers();
7 | TgList& globalChats();
8 | QString prepareDialogItemMessage(QString text, TgList entities);
9 | QString messageToHtml(QString text, TgList entities);
10 | void handleMessageAction(TgObject &row, TgObject message, TgObject sender, TgList users, TgList chats);
11 |
12 | #endif // MESSAGEUTIL_H
13 |
--------------------------------------------------------------------------------
/qml/control/Spinner.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | width: 40 * kgScaling
5 | height: width
6 |
7 | property bool white: false
8 |
9 | Image {
10 | asynchronous: true
11 | anchors.centerIn: parent
12 | source: white ? "../../img/loading_white.png" : "../../img/loading.png"
13 | width: 20 * kgScaling
14 | height: width
15 | smooth: true
16 |
17 | PropertyAnimation on rotation {
18 | loops: Animation.Infinite
19 | from: 0
20 | to: 360
21 | easing.type: Easing.InOutQuad
22 | duration: 1400
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android/res/values/libs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - https://download.qt-project.org/ministro/android/qt5/qt-5.4
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/qml/dialog/FolderItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | height: ListView.view.height
5 | width: tabContent.width + 10 * kgScaling
6 |
7 | Row {
8 | id: tabContent
9 | anchors.centerIn: parent
10 | spacing: 5 * kgScaling
11 |
12 | //TODO: only icon, only text, text + icon
13 | Image {
14 | source: icon
15 | smooth: true
16 | width: folderText.font.pixelSize * 1.5
17 | height: width
18 | asynchronous: true
19 | visible: icon.length != 0
20 | }
21 |
22 | Text {
23 | id: folderText
24 | text: title
25 | visible: title.length != 0
26 | color: "#FFFFFF"
27 | font.pixelSize: 12 * kgScaling
28 | }
29 | }
30 |
31 | MouseArea {
32 | anchors.fill: parent
33 |
34 | onClicked: {
35 | currentFolderIndex = index
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/qml/auth/Button.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | id: buttonRoot
5 | signal clicked()
6 |
7 | property bool enabled: true
8 |
9 | width: buttonText.width + 40 * kgScaling
10 | height: 40 * kgScaling
11 |
12 | Rectangle {
13 | anchors.fill: parent
14 | radius: 5
15 | color: globalAccent
16 | }
17 |
18 | Text {
19 | id: buttonText
20 | anchors.centerIn: parent
21 | text: "Next"
22 | font.bold: true
23 | color: "#FFFFFF"
24 | font.pixelSize: 12 * kgScaling
25 | }
26 |
27 | MouseArea {
28 | id: innerArea
29 | anchors.fill: parent
30 | enabled: buttonRoot.enabled
31 | }
32 |
33 | Component.onCompleted: {
34 | innerArea.clicked.connect(buttonRoot.clicked);
35 | }
36 |
37 | Rectangle {
38 | anchors.fill: parent
39 | radius: 5 * kgScaling
40 | color: "#000000"
41 | opacity: 0.1
42 | visible: activeFocus
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/currentuserinfo.h:
--------------------------------------------------------------------------------
1 | #ifndef CURRENTUSERINFO_H
2 | #define CURRENTUSERINFO_H
3 |
4 | #include
5 | #include
6 | #include "tgclient.h"
7 | #include "avatardownloader.h"
8 |
9 | class CurrentUserInfo : public QObject
10 | {
11 | Q_OBJECT
12 | Q_PROPERTY(QObject* client READ client WRITE setClient)
13 | Q_PROPERTY(QObject* avatarDownloader READ avatarDownloader WRITE setAvatarDownloader)
14 |
15 | private:
16 | QMutex _mutex;
17 | TgClient* _client;
18 |
19 | TgLongVariant _userId;
20 | TgLongVariant _requestId;
21 |
22 | AvatarDownloader* _avatarDownloader;
23 |
24 | public:
25 | explicit CurrentUserInfo(QObject *parent = 0);
26 |
27 | void setClient(QObject *client);
28 | QObject* client() const;
29 |
30 | void setAvatarDownloader(QObject *client);
31 | QObject* avatarDownloader() const;
32 |
33 | signals:
34 | void userInfoChanged(QString name, QString username, QColor thumbnailColor, QString thumbnailText);
35 | void userAvatarDownloaded(QString avatar);
36 |
37 | public slots:
38 | void authorized(TgLongVariant userId);
39 | void usersGetUsersResponse(TgVector data, TgLongVariant messageId);
40 | void avatarDownloaded(TgLongVariant photoId, QString filePath);
41 |
42 | };
43 |
44 | #endif // CURRENTUSERINFO_H
45 |
--------------------------------------------------------------------------------
/qml/control/DrawerButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | width: ListView.view.width
5 | height: 40 * kgScaling
6 |
7 | Item {
8 | id: actionIcon
9 | anchors.top: parent.top
10 | anchors.bottom: parent.bottom
11 | anchors.left: parent.left
12 | width: height
13 |
14 | Image {
15 | anchors.centerIn: parent
16 | asynchronous: true
17 | source: icon
18 | smooth: true
19 | width: 20 * kgScaling
20 | height: width
21 | }
22 | }
23 |
24 | MouseArea {
25 | anchors.fill: parent
26 | onClicked: {
27 | switch (index) {
28 | case 0:
29 | telegramClient.resetSession();
30 | break;
31 | case 1:
32 | platformUtils.quit();
33 | break;
34 | }
35 |
36 | drawerRoot.closeDrawer();
37 | }
38 | }
39 |
40 | Text {
41 | anchors.left: actionIcon.right
42 | anchors.top: parent.top
43 | anchors.right: parent.right
44 | anchors.bottom: parent.bottom
45 | verticalAlignment: Text.AlignVCenter
46 | elide: Text.ElideRight
47 | text: name
48 | font.pixelSize: 12 * kgScaling
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/qml/message/MessagePage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import Kutegram 1.0
3 |
4 | Rectangle {
5 | property string globalState: "NO_SELECT"
6 | property alias messageEdit: messageEdit
7 | property alias messagesView: messagesView
8 |
9 | ListView {
10 | id: messagesView
11 | anchors.top: parent.top
12 | anchors.left: parent.left
13 | anchors.right: parent.right
14 | anchors.bottom: messageEdit.top
15 | clip: true
16 |
17 | boundsBehavior: Flickable.StopAtBounds
18 |
19 | anchors.topMargin: Math.max(0, parent.height - messageEdit.height - childrenRect.height)
20 |
21 | cacheBuffer: Math.max(parent.height / 6, 0)
22 |
23 | onMovementEnded: {
24 | if (atYBeginning && messagesModel.canFetchMoreUpwards()) {
25 | messagesModel.fetchMoreUpwards();
26 | }
27 | if (atYEnd && messagesModel.canFetchMoreDownwards()) {
28 | messagesModel.canFetchMoreDownwards();
29 | }
30 | }
31 |
32 | model: messagesModel
33 |
34 | delegate: MessageItem {
35 | state: globalState
36 | }
37 | }
38 |
39 | //TODO Hide MessageEdit when user is restricted
40 | MessageEdit {
41 | id: messageEdit
42 | anchors.left: parent.left
43 | anchors.right: parent.right
44 | anchors.bottom: parent.bottom
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/avatardownloader.h:
--------------------------------------------------------------------------------
1 | #ifndef AVATARDOWNLOADER_H
2 | #define AVATARDOWNLOADER_H
3 |
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 | #include "tgclient.h"
10 |
11 | class AvatarDownloader : public QObject
12 | {
13 | Q_OBJECT
14 | Q_PROPERTY(QObject* client READ client WRITE setClient)
15 |
16 | private:
17 | QMutex _mutex;
18 | TgClient* _client;
19 | TgLongVariant _userId;
20 | QHash _requestsAvatars;
21 | QHash _requestsPhotos;
22 | TgList _downloadedAvatars;
23 | TgList _downloadedPhotos;
24 |
25 | public:
26 | explicit AvatarDownloader(QObject *parent = 0);
27 | void readDatabase();
28 | void saveDatabase();
29 |
30 | void setClient(QObject *client);
31 | QObject* client() const;
32 |
33 | signals:
34 | void avatarDownloaded(TgLongVariant photoId, QString filePath);
35 | void photoDownloaded(TgLongVariant photoId, QString filePath);
36 |
37 | public slots:
38 | void authorized(TgLongVariant userId);
39 | void fileDownloaded(TgLongVariant fileId, QString filePath);
40 | void fileDownloadCanceled(TgLongVariant fileId, QString filePath);
41 |
42 | qint64 downloadAvatar(TgObject peer);
43 | qint64 downloadPhoto(TgObject photo);
44 |
45 | static QString getAvatarText(QString title);
46 | static QColor userColor(TgLongVariant id);
47 |
48 | };
49 |
50 | #endif // AVATARDOWNLOADER_H
51 |
52 |
--------------------------------------------------------------------------------
/qmlapplicationviewer/qmlapplicationviewer.h:
--------------------------------------------------------------------------------
1 | // checksum 0x382f version 0x6000f
2 | /*
3 | This file was generated by the Qt Quick Application wizard of Qt Creator.
4 | QmlApplicationViewer is a convenience class containing mobile device specific
5 | code such as screen orientation handling. Also QML paths and debugging are
6 | handled here.
7 | It is recommended not to modify this file, since newer versions of Qt Creator
8 | may offer an updated version of it.
9 | */
10 |
11 | #ifndef QMLAPPLICATIONVIEWER_H
12 | #define QMLAPPLICATIONVIEWER_H
13 |
14 | #include
15 |
16 | #if QT_VERSION >= 0x050000
17 | #include
18 | class QmlApplicationViewer : public QQuickView
19 | #else
20 | #include
21 | class QmlApplicationViewer : public QDeclarativeView
22 | #endif
23 | {
24 | Q_OBJECT
25 |
26 | public:
27 | enum ScreenOrientation {
28 | ScreenOrientationLockPortrait,
29 | ScreenOrientationLockLandscape,
30 | ScreenOrientationAuto
31 | };
32 |
33 | explicit QmlApplicationViewer(QObject *parent = 0);
34 | virtual ~QmlApplicationViewer();
35 |
36 | void setMainQmlFile(const QString &file);
37 | void addImportPath(const QString &path);
38 |
39 | // Note that this will only have an effect on Symbian and Fremantle.
40 | void setOrientation(ScreenOrientation orientation);
41 |
42 | void showExpanded();
43 |
44 | private:
45 | class QmlApplicationViewerPrivate *m_d;
46 | };
47 |
48 | #endif // QMLAPPLICATIONVIEWER_H
49 |
--------------------------------------------------------------------------------
/qml/auth/CodePage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import "../control"
3 |
4 | Rectangle {
5 |
6 | Column {
7 | anchors.centerIn: parent
8 | width: parent.width * 2 / 3
9 | spacing: 5
10 |
11 | Text {
12 | anchors.left: parent.left
13 | anchors.right: parent.right
14 | text: "Confirmation Code"
15 | font.bold: true
16 | wrapMode: Text.Wrap
17 | font.pixelSize: 12 * kgScaling
18 | }
19 |
20 | Text {
21 | anchors.left: parent.left
22 | anchors.right: parent.right
23 | text: "Please, enter your confirmation code."
24 | wrapMode: Text.Wrap
25 | font.pixelSize: 12 * kgScaling
26 | }
27 |
28 | LineEdit {
29 | id: codeEdit
30 | anchors.left: parent.left
31 | anchors.right: parent.right
32 | }
33 |
34 | Button {
35 | anchors.left: parent.left
36 | anchors.right: parent.right
37 | enabled: !root.authProgress
38 | onClicked: {
39 | if (codeEdit.text.length == 0) {
40 | snackBar.text = "You have entered an invalid code.";
41 | return;
42 | }
43 |
44 | snackBar.close();
45 |
46 | setAuthProgress(true);
47 | telegramClient.authSignIn(phonePage.phoneNumber, phonePage.phoneCodeHash, codeEdit.text);
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/qtc_packaging/debian_harmattan/copyright:
--------------------------------------------------------------------------------
1 | This package was debianized by unknown <> on
2 | Wed, 09 Aug 2023 11:02:57 +0300.
3 |
4 | It was downloaded from
5 |
6 | Upstream Author(s):
7 |
8 |
9 |
10 |
11 | Copyright:
12 |
13 |
14 |
15 |
16 | License:
17 |
18 | This package is free software; you can redistribute it and/or modify
19 | it under the terms of the GNU General Public License as published by
20 | the Free Software Foundation; either version 2 of the License, or
21 | (at your option) any later version.
22 |
23 | This package is distributed in the hope that it will be useful,
24 | but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | GNU General Public License for more details.
27 |
28 | You should have received a copy of the GNU General Public License
29 | along with this package; if not, write to the Free Software
30 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
31 |
32 | On Debian systems, the complete text of the GNU General
33 | Public License can be found in `/usr/share/common-licenses/GPL'.
34 |
35 | The Debian packaging is (C) 2023, unknown <> and
36 | is licensed under the GPL, see above.
37 |
38 |
39 | # Please also look if there are files or directories which have a
40 | # different copyright/license attached and list them here.
41 |
--------------------------------------------------------------------------------
/src/foldersmodel.h:
--------------------------------------------------------------------------------
1 | #ifndef FOLDERSMODEL_H
2 | #define FOLDERSMODEL_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include "tgclient.h"
8 |
9 | class FoldersModel : public QAbstractListModel
10 | {
11 | Q_OBJECT
12 | Q_PROPERTY(QObject* client READ client WRITE setClient)
13 |
14 | private:
15 | QMutex _mutex;
16 | QList _folders;
17 |
18 | TgClient* _client;
19 | TgLongVariant _userId;
20 |
21 | TgLongVariant _requestId;
22 |
23 | enum FolderRoles {
24 | TitleRole = Qt::UserRole + 1,
25 | IconRole,
26 | FolderIndexRole
27 | };
28 |
29 | public:
30 | explicit FoldersModel(QObject *parent = 0);
31 | void resetState();
32 |
33 | QHash roleNames() const;
34 |
35 | void setClient(QObject *client);
36 | QObject* client() const;
37 |
38 | int rowCount(const QModelIndex& parent = QModelIndex()) const;
39 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
40 |
41 | TgObject createRow(TgObject filter);
42 | QList folders();
43 |
44 | signals:
45 | void foldersChanged(QList folders);
46 |
47 | public slots:
48 | void authorized(TgLongVariant userId);
49 | void messagesGetDialogFiltersResponse(TgVector data, TgLongVariant messageId);
50 |
51 | void refresh();
52 |
53 | bool canFetchMoreDownwards() const;
54 | void fetchMoreDownwards();
55 |
56 | static bool matchesFilter(TgObject filter, TgObject peer);
57 |
58 | };
59 |
60 | #endif // FOLDERSMODEL_H
61 |
--------------------------------------------------------------------------------
/src/platformutils.h:
--------------------------------------------------------------------------------
1 | #ifndef PLATFORMUTILS_H
2 | #define PLATFORMUTILS_H
3 |
4 | #include
5 | #include
6 | #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINPHONE)
7 | #include
8 | #include
9 | #endif
10 | #include
11 | #include
12 | #include
13 |
14 | #ifdef SYMBIAN3_READY
15 | #include "QPiglerAPI.h"
16 | #endif
17 |
18 | class PlatformUtils : public QObject
19 | {
20 | Q_OBJECT
21 | private:
22 | QWidget* window;
23 | #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINPHONE)
24 | QSystemTrayIcon trayIcon;
25 | QMenu trayMenu;
26 | #endif
27 | QHash unread;
28 | #ifdef SYMBIAN3_READY
29 | QPiglerAPI pigler;
30 | qint32 piglerId;
31 | #endif
32 |
33 | public:
34 | explicit PlatformUtils(QObject *parent = 0);
35 |
36 | signals:
37 |
38 | public slots:
39 | void showAndRaise();
40 | void quit();
41 |
42 | #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINPHONE)
43 | void trayActivated(QSystemTrayIcon::ActivationReason reason);
44 | void messageClicked();
45 | void menuTriggered(QAction* action);
46 | #endif
47 |
48 | #ifdef SYMBIAN3_READY
49 | void piglerHandleTap(qint32 notificationId);
50 | #endif
51 |
52 | void windowsExtendFrameIntoClientArea(int left, int top, int right, int bottom);
53 | bool windowsIsCompositionEnabled();
54 | QColor windowsRealColorizationColor();
55 | bool isWindows();
56 |
57 | void gotNewMessage(qint64 peerId, QString peerName, QString senderName, QString text, bool silent);
58 | };
59 |
60 | void openUrl(QUrl url);
61 |
62 | #endif // PLATFORMUTILS_H
63 |
--------------------------------------------------------------------------------
/qml/dialog/DialogPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import Kutegram 1.0
3 |
4 | Rectangle {
5 | id: pageRoot
6 | signal refresh()
7 |
8 | ListView {
9 | id: folderSlide
10 |
11 | anchors.fill: parent
12 | clip: true
13 |
14 | model: foldersModel
15 | cacheBuffer: width * 5
16 |
17 | boundsBehavior: Flickable.StopAtBounds
18 | orientation: ListView.Horizontal
19 | snapMode: ListView.SnapOneItem
20 | highlightRangeMode: ListView.StrictlyEnforceRange
21 | highlightFollowsCurrentItem: true
22 | highlightMoveDuration: 200
23 |
24 | currentIndex: currentFolderIndex
25 | onCurrentItemChanged: {
26 | currentFolderIndex = currentIndex
27 | }
28 |
29 | delegate: ListView {
30 | id: dialogsView
31 | width: folderSlide.width
32 | height: folderSlide.height
33 | cacheBuffer: pageRoot.height / 6
34 |
35 | clip: true
36 |
37 | boundsBehavior: Flickable.StopAtBounds
38 |
39 | onMovementEnded: {
40 | if (atYEnd && dialogsModel.canFetchMoreDownwards()) {
41 | dialogsModel.canFetchMoreDownwards();
42 | }
43 | }
44 |
45 | model: dialogsModel
46 |
47 | delegate: Repeater {
48 | id: dialogRepeater
49 |
50 | model: dialogsModel.inFolder(index, folderIndex)
51 | height: count != 0 ? 40 * kgScaling : 0
52 |
53 | DialogItem {
54 | y: dialogRepeater.y
55 | width: dialogsView.width
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 |
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:1.1.0'
8 | }
9 | }
10 |
11 | allprojects {
12 | repositories {
13 | jcenter()
14 | }
15 | }
16 |
17 | apply plugin: 'com.android.application'
18 |
19 | dependencies {
20 | compile fileTree(dir: 'libs', include: ['*.jar'])
21 | }
22 |
23 | android {
24 | /*******************************************************
25 | * The following variables:
26 | * - androidBuildToolsVersion,
27 | * - androidCompileSdkVersion
28 | * - qt5AndroidDir - holds the path to qt android files
29 | * needed to build any Qt application
30 | * on Android.
31 | *
32 | * are defined in gradle.properties file. This file is
33 | * updated by QtCreator and androiddeployqt tools.
34 | * Changing them manually might break the compilation!
35 | *******************************************************/
36 |
37 | compileSdkVersion androidCompileSdkVersion.toInteger()
38 |
39 | buildToolsVersion androidBuildToolsVersion
40 |
41 | sourceSets {
42 | main {
43 | manifest.srcFile 'AndroidManifest.xml'
44 | java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
45 | aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
46 | res.srcDirs = [qt5AndroidDir + '/res', 'res']
47 | resources.srcDirs = ['src']
48 | renderscript.srcDirs = ['src']
49 | assets.srcDirs = ['assets']
50 | jniLibs.srcDirs = ['libs']
51 | }
52 | }
53 |
54 | lintOptions {
55 | abortOnError false
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/qml/auth/IntroPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Rectangle {
4 | id: introPageRect
5 |
6 | Column {
7 | anchors.centerIn: parent
8 | width: parent.width * 2 / 3
9 | spacing: 5
10 |
11 | Rectangle {
12 | anchors.horizontalCenter: parent.horizontalCenter
13 | width: Math.min(introPageRect.height / 2, parent.width * 2 / 3)
14 | height: width
15 | color: globalAccent
16 | radius: width / 4
17 |
18 | Image {
19 | anchors.fill: parent
20 | //asynchronous: true
21 | smooth: true
22 | source: "../../kutegramquick_big.png"
23 | }
24 | }
25 |
26 | Text {
27 | anchors.left: parent.left
28 | anchors.right: parent.right
29 | text: "Kutegram"
30 | font.bold: true
31 | wrapMode: Text.Wrap
32 | horizontalAlignment: Text.AlignHCenter
33 | font.pixelSize: 12 * kgScaling
34 | }
35 |
36 | Text {
37 | anchors.left: parent.left
38 | anchors.right: parent.right
39 | text: "Just another unofficial Telegram client."
40 | wrapMode: Text.Wrap
41 | horizontalAlignment: Text.AlignHCenter
42 | font.pixelSize: 12 * kgScaling
43 | }
44 |
45 | Button {
46 | anchors.left: parent.left
47 | anchors.right: parent.right
48 | enabled: !root.authProgress
49 | onClicked: {
50 | telegramClient.resetSession();
51 | root.setAuthProgress(true);
52 | telegramClient.start();
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/qml/auth/PhonePage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import "../control"
3 |
4 | Rectangle {
5 | property string phoneNumber;
6 | property string phoneCodeHash;
7 |
8 | Column {
9 | anchors.centerIn: parent
10 | width: parent.width * 2 / 3
11 | spacing: 5
12 |
13 | Text {
14 | anchors.left: parent.left
15 | anchors.right: parent.right
16 | text: "Your Phone Number"
17 | font.bold: true
18 | wrapMode: Text.Wrap
19 | font.pixelSize: 12 * kgScaling
20 | }
21 |
22 | Text {
23 | anchors.left: parent.left
24 | anchors.right: parent.right
25 | text: "Please, enter your phone number."
26 | wrapMode: Text.Wrap
27 | font.pixelSize: 12 * kgScaling
28 | }
29 |
30 | LineEdit {
31 | id: phoneEdit
32 | anchors.left: parent.left
33 | anchors.right: parent.right
34 | }
35 |
36 | Button {
37 | anchors.left: parent.left
38 | anchors.right: parent.right
39 | enabled: !root.authProgress
40 | onClicked: {
41 | if (phoneEdit.text.length == 0) {
42 | snackBar.text = "Invalid phone number. Please try again.";
43 | return;
44 | }
45 |
46 | snackBar.close();
47 |
48 | phoneNumber = phoneEdit.text;
49 | phoneNumber.replace(' ', "");
50 | phoneNumber.replace('-', "");
51 |
52 | phoneCodeHash = "";
53 |
54 | setAuthProgress(true);
55 | telegramClient.authSendCode(phoneNumber);
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/qml/control/LineEdit.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | width: 160 * kgScaling
5 | height: 40 * kgScaling
6 |
7 | property alias text: innerInput.text
8 |
9 | state: innerInput.activeFocus ? "FOCUSED" : "NOT_FOCUSED"
10 |
11 | states: [
12 | State {
13 | name: "NOT_FOCUSED"
14 | PropertyChanges {
15 | target: focusedRect
16 | width: 0
17 | }
18 | },
19 | State {
20 | name: "FOCUSED"
21 | PropertyChanges {
22 | target: focusedRect
23 | width: parent.width - 10 * kgScaling
24 | }
25 | }
26 | ]
27 |
28 | transitions: [
29 | Transition {
30 | NumberAnimation {
31 | properties: "width"
32 | easing.type: Easing.InOutQuad
33 | duration: 200
34 | }
35 | }
36 | ]
37 |
38 | MouseArea {
39 | anchors.fill: parent
40 | onClicked: {
41 | innerInput.forceActiveFocus();
42 | }
43 | }
44 |
45 | TextInput {
46 | id: innerInput
47 | anchors.left: parent.left
48 | anchors.right: parent.right
49 | anchors.verticalCenter: parent.verticalCenter
50 | anchors.leftMargin: 5 * kgScaling
51 | anchors.rightMargin: 5 * kgScaling
52 | font.pixelSize: 12 * kgScaling
53 | }
54 |
55 | Rectangle {
56 | anchors.horizontalCenter: parent.horizontalCenter
57 | anchors.bottom: parent.bottom
58 | width: parent.width - 10 * kgScaling
59 | height: 2 * kgScaling
60 | color: "#999999"
61 | }
62 |
63 | Rectangle {
64 | id: focusedRect
65 | anchors.horizontalCenter: parent.horizontalCenter
66 | anchors.bottom: parent.bottom
67 | width: parent.width - 10 * kgScaling
68 | height: 2 * kgScaling
69 | color: globalAccent
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/kutegramquick.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/qtc_packaging/debian_harmattan/rules:
--------------------------------------------------------------------------------
1 | #!/usr/bin/make -f
2 | # -*- makefile -*-
3 | # Sample debian/rules that uses debhelper.
4 | # This file was originally written by Joey Hess and Craig Small.
5 | # As a special exception, when this file is copied by dh-make into a
6 | # dh-make output file, you may use that output file without restriction.
7 | # This special exception was added by Craig Small in version 0.37 of dh-make.
8 |
9 | # Uncomment this to turn on verbose mode.
10 | #export DH_VERBOSE=1
11 |
12 |
13 |
14 |
15 |
16 | configure: configure-stamp
17 | configure-stamp:
18 | dh_testdir
19 | # qmake PREFIX=/usr# Uncomment this line for use without Qt Creator
20 |
21 | touch configure-stamp
22 |
23 |
24 | build: build-stamp
25 |
26 | build-stamp: configure-stamp
27 | dh_testdir
28 |
29 | # Add here commands to compile the package.
30 | # $(MAKE) # Uncomment this line for use without Qt Creator
31 | #docbook-to-man debian/kutegramquick.sgml > kutegramquick.1
32 |
33 | touch $@
34 |
35 | clean:
36 | dh_testdir
37 | dh_testroot
38 | rm -f build-stamp configure-stamp
39 |
40 | # Add here commands to clean up after the build process.
41 | $(MAKE) clean
42 |
43 | dh_clean
44 |
45 | install: build
46 | dh_testdir
47 | dh_testroot
48 | dh_clean -k
49 | dh_installdirs
50 |
51 | # Add here commands to install the package into debian/kutegramquick.
52 | $(MAKE) INSTALL_ROOT="$(CURDIR)"/debian/kutegramquick install
53 |
54 |
55 | # Build architecture-independent files here.
56 | binary-indep: build install
57 | # We have nothing to do by default.
58 |
59 | # Build architecture-dependent files here.
60 | binary-arch: build install
61 | dh_testdir
62 | dh_testroot
63 | dh_installchangelogs
64 | dh_installdocs
65 | dh_installexamples
66 | # dh_install
67 | # dh_installmenu
68 | # dh_installdebconf
69 | # dh_installlogrotate
70 | # dh_installemacsen
71 | # dh_installpam
72 | # dh_installmime
73 | # dh_python
74 | # dh_installinit
75 | # dh_installcron
76 | # dh_installinfo
77 | dh_installman
78 | dh_link
79 | dh_strip
80 | dh_compress
81 | dh_fixperms
82 | # dh_perl
83 | # dh_makeshlibs
84 | dh_installdeb
85 | # dh_shlibdeps # Uncomment this line for use without Qt Creator
86 | dh_gencontrol
87 | dh_md5sums
88 | dh_builddeb
89 |
90 | binary: binary-indep binary-arch
91 | .PHONY: build clean binary-indep binary-arch binary install configure
92 |
--------------------------------------------------------------------------------
/qml/message/MessageImage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import "../control"
3 |
4 | Image {
5 | id: imageRoot
6 | height: width
7 | //height: sourceSize.height * width / sourceSize.width
8 | source: photoFile.length == 0 ? "" : photoFile + ".thumbnail.jpg"
9 | clip: true
10 | asynchronous: true
11 | smooth: true
12 | fillMode: Image.PreserveAspectFit
13 |
14 | Repeater {
15 | model: imageRoot.status == Image.Ready ? 0 : 1
16 | Spinner {
17 | anchors.centerIn: imageRoot
18 | white: false
19 | }
20 | }
21 |
22 | Rectangle {
23 | id: spoilerRect
24 | visible: photoSpoiler
25 | anchors.fill: parent
26 | color: "gray"
27 |
28 | Text {
29 | anchors.fill: parent
30 | verticalAlignment: Text.AlignVCenter
31 | horizontalAlignment: Text.AlignHCenter
32 | font.pixelSize: 12 * kgScaling
33 | color: "white"
34 | text: "Media is hidden.\nClick to reveal."
35 | }
36 | }
37 |
38 | Image {
39 | id: checkbox
40 | //TODO: adaptive color
41 | source: "../../img/checkbox-blank-circle-outline.png"
42 | smooth: true
43 | width: 20 * kgScaling
44 | height: width
45 | x: 5 * kgScaling
46 | y: 5 * kgScaling
47 | asynchronous: true
48 | }
49 |
50 | MouseArea {
51 | anchors.fill: parent
52 | onClicked: {
53 | if (spoilerRect.visible) {
54 | spoilerRect.visible = false;
55 | return;
56 | }
57 |
58 | imageViewer.imageSource = photoFile;
59 | imageViewer.state = "OPENED";
60 | }
61 | }
62 |
63 | transitions: [
64 | Transition {
65 | NumberAnimation {
66 | properties: "x,opacity"
67 | easing.type: Easing.InOutQuad
68 | duration: 200
69 | }
70 | }
71 | ]
72 |
73 | states: [
74 | State {
75 | name: "NO_SELECT"
76 | PropertyChanges {
77 | target: checkbox
78 | opacity: 0
79 | x: -15 * kgScaling
80 | }
81 | },
82 | State {
83 | name: "SHOW_SELECT"
84 | PropertyChanges {
85 | target: checkbox
86 | opacity: 1
87 | x: 5 * kgScaling
88 | }
89 | }
90 | ]
91 | }
92 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/src/dialogsmodel.h:
--------------------------------------------------------------------------------
1 | #ifndef DIALOGSMODEL_H
2 | #define DIALOGSMODEL_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include "tgclient.h"
8 | #include "avatardownloader.h"
9 | #include "foldersmodel.h"
10 |
11 | class DialogsModel : public QAbstractListModel
12 | {
13 | Q_OBJECT
14 | Q_PROPERTY(QObject* client READ client WRITE setClient)
15 | Q_PROPERTY(QObject* avatarDownloader READ avatarDownloader WRITE setAvatarDownloader)
16 | Q_PROPERTY(QObject* folders READ folders WRITE setFolders)
17 |
18 | private:
19 | QMutex _mutex;
20 | QList _dialogs;
21 |
22 | TgClient* _client;
23 | TgLongVariant _userId;
24 |
25 | TgLongVariant _requestId;
26 | TgObject _offsets;
27 |
28 | AvatarDownloader* _avatarDownloader;
29 |
30 | FoldersModel* _folders;
31 | qint32 _lastPinnedIndex;
32 |
33 | enum DialogRoles {
34 | TitleRole = Qt::UserRole + 1,
35 | ThumbnailColorRole,
36 | ThumbnailTextRole,
37 | AvatarRole,
38 | MessageTimeRole,
39 | MessageTextRole,
40 | TooltipRole,
41 | PeerBytesRole,
42 | MessageSenderNameRole,
43 | MessageSenderColorRole
44 | };
45 |
46 | public:
47 | explicit DialogsModel(QObject *parent = 0);
48 | void resetState();
49 |
50 | QHash roleNames() const;
51 |
52 | void setClient(QObject *client);
53 | QObject* client() const;
54 |
55 | void setAvatarDownloader(QObject *client);
56 | QObject* avatarDownloader() const;
57 |
58 | void setFolders(QObject *model);
59 | QObject* folders() const;
60 |
61 | int rowCount(const QModelIndex& parent = QModelIndex()) const;
62 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
63 |
64 | TgObject createRow(TgObject dialog, TgObject peer, TgObject message, TgObject messageSender, QList folders, TgList users, TgList chats);
65 | void handleDialogMessage(TgObject &row, TgObject message, TgObject messageSender, TgList users, TgList chats);
66 | void prepareNotification(TgObject row);
67 |
68 | signals:
69 | void sendNotification(qint64 peerId, QString peerName, QString senderName, QString text, bool silent);
70 |
71 | public slots:
72 | void authorized(TgLongVariant userId);
73 | void messagesGetDialogsResponse(TgObject data, TgLongVariant messageId);
74 | void avatarDownloaded(TgLongVariant photoId, QString filePath);
75 |
76 | void refresh();
77 |
78 | bool canFetchMoreDownwards() const;
79 | void fetchMoreDownwards();
80 |
81 | void foldersChanged(QList folders);
82 | bool inFolder(qint32 index, qint32 folderIndex);
83 |
84 | void gotUpdate(TgObject update, TgLongVariant messageId, TgList users, TgList chats, qint32 date, qint32 seq, qint32 seqStart);
85 | void gotMessageUpdate(TgObject update, TgLongVariant messageId);
86 |
87 | };
88 |
89 | #endif // DIALOGSMODEL_H
90 |
--------------------------------------------------------------------------------
/src/currentuserinfo.cpp:
--------------------------------------------------------------------------------
1 | #include "currentuserinfo.h"
2 |
3 | #include
4 |
5 | CurrentUserInfo::CurrentUserInfo(QObject *parent)
6 | : QObject(parent)
7 | , _mutex(QMutex::Recursive)
8 | , _client(0)
9 | , _userId(0)
10 | , _requestId(0)
11 | , _avatarDownloader(0)
12 | {
13 | }
14 |
15 | void CurrentUserInfo::setClient(QObject *client)
16 | {
17 | QMutexLocker lock(&_mutex);
18 |
19 | if (_client) {
20 | _client->disconnect(this);
21 | }
22 |
23 | _client = dynamic_cast(client);
24 | _userId = 0;
25 |
26 | _requestId = 0;
27 | emit userInfoChanged("", "", AvatarDownloader::userColor(0), "");
28 |
29 | if (!_client) return;
30 |
31 | connect(_client, SIGNAL(authorized(TgLongVariant)), this, SLOT(authorized(TgLongVariant)));
32 | connect(_client, SIGNAL(vectorUserResponse(TgVector,TgLongVariant)), this, SLOT(usersGetUsersResponse(TgVector,TgLongVariant)));
33 | }
34 |
35 | QObject* CurrentUserInfo::client() const
36 | {
37 | return _client;
38 | }
39 |
40 | void CurrentUserInfo::setAvatarDownloader(QObject *avatarDownloader)
41 | {
42 | QMutexLocker lock(&_mutex);
43 |
44 | if (_avatarDownloader) {
45 | _avatarDownloader->disconnect(this);
46 | }
47 |
48 | _avatarDownloader = dynamic_cast(avatarDownloader);
49 |
50 | if (!_avatarDownloader) return;
51 |
52 | connect(_avatarDownloader, SIGNAL(avatarDownloaded(TgLongVariant,QString)), this, SLOT(avatarDownloaded(TgLongVariant,QString)));
53 | }
54 |
55 | QObject* CurrentUserInfo::avatarDownloader() const
56 | {
57 | return _avatarDownloader;
58 | }
59 |
60 | void CurrentUserInfo::authorized(TgLongVariant userId)
61 | {
62 | QMutexLocker lock(&_mutex);
63 |
64 | if (_userId != userId) {
65 | _requestId = 0;
66 | emit userInfoChanged("", "", AvatarDownloader::userColor(0), "");
67 | _userId = userId;
68 | }
69 | }
70 |
71 | void CurrentUserInfo::usersGetUsersResponse(TgVector data, TgLongVariant messageId)
72 | {
73 | QMutexLocker lock(&_mutex);
74 |
75 | for (qint32 i = 0; i < data.size(); ++i) {
76 | TgObject obj = data.first().toMap();
77 |
78 | if (_client->getUserId() != TgClient::getPeerId(obj)) {
79 | continue;
80 | }
81 |
82 | QString name = obj["first_name"].toString() + " " + obj["last_name"].toString();
83 | emit userInfoChanged(name, obj["username"].toString(), AvatarDownloader::userColor(obj["id"]), AvatarDownloader::getAvatarText(name));
84 |
85 | if (_avatarDownloader)
86 | _requestId = _avatarDownloader->downloadAvatar(obj);
87 |
88 | return;
89 | }
90 | }
91 |
92 | void CurrentUserInfo::avatarDownloaded(TgLongVariant photoId, QString filePath)
93 | {
94 | QMutexLocker lock(&_mutex);
95 |
96 | if (_requestId != photoId) {
97 | return;
98 | }
99 |
100 | _requestId = 0;
101 | emit userAvatarDownloaded(filePath);
102 | }
103 |
--------------------------------------------------------------------------------
/qtc_packaging/debian_harmattan/control:
--------------------------------------------------------------------------------
1 | Source: kutegramquick
2 | Section: user/other
3 | Priority: optional
4 | Maintainer: unknown <>
5 | Build-Depends: debhelper (>= 5), libqt4-dev
6 | Standards-Version: 3.7.3
7 | Homepage:
8 |
9 | Package: kutegramquick
10 | Architecture: any
11 | Depends: ${shlibs:Depends}, ${misc:Depends}
12 | Description:
13 |
14 | XSBC-Maemo-Display-Name: kutegramquick
15 | XB-Maemo-Icon-26: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAB8RJREFUaIHtmWtsVMcVx3/n3l3vw2vv2oBZzCu80kahiPCqmhdBLWlTRYrUSIRQoQhaAe2HqrRVSSulxY1aNYqqkC9taWiUSlHUikhtJScFWuJS0pRHGyAkvGyDg7ExrPH6sV7b67339MMuu3vXuzZrm0iR+H/x3ZkzM///zJkzZ8bwKYcULN0yP0jU8k0Lu8u3PTm32m3atpW0b9wbqWldt26v9QlzHBVZAVvmB0kaz6FsXTy/PPDoyhDBgIlqgVaqNnAFpF3Rj1HZhyEtpiVtlg5fqltzKPnJC9i08HngWRDX99fXEiw3sVUptkgOqNoqDAgSBT2i0IwtTaaphy3DHaktu9G3dcX/hm+zgEUdCNMNgZ2bZ2PbmmOieX9HVQMIqqhgX0WkA7isIvtBL1pJq9W8Pr25bt3exOQJeGauF7NsAIV5Mzw881jNCEKFiZJTl/97bKREakSRiIge3/lQw6bS6IMLAI/pIe21blc+gWKECtkV2jDFIYKA1AjUAL6SGqdhjE6sNEIjRd16e1UpdTDg5goUHdTp94ZAbNAiEh3mc+FVzKoKU+71c62/ic6BVvoTUUSMvPZjUi8w7oQEFIYAJxtjtLZ7+dqSp9i2aitedxkiQsIaoGuwnXOdh/nn5T/kiUjTUxtNkzTEJDsxpbveGAIK74GkpXx4wc9LT/2Ux5asRiRrV2b6CJcvIFw+n0s9J/i454NMvapw7EwvO778PWwZpKO/iQtdR/LGEUrZ/GMIyIUiIkSiw7y+/xqRXR/hMrNN+hIRDjUdwGPPYc09X8Bluti05GWao8d58/zzJKx+2joTNJzo4e2tTwPQ0n2SC13/GTfhfBTYxHl7QOFUUz9lEnSQjw/3UN/4Ekcjr7Bxz3b+dKw+UzcvtIx7pjwEAh3RBG5jXAFmvAKcMxMbsDh7uZ8HFi13lJ+9cZgL0aO4TINQaJAX/7aHq93XUp2KyeJpX8SylLbrg4QrpmbaKYqtdjobGb/vFxGQGxFS3yca+xka9PKDr3zDYXm8/S+oWoCwZlkV566f59cHX8/Uzw8tY19DkHe+828uvHAwU35XaCk/vv9tvr38Vaq8tZMtQHL+pr6vRxNMC1Qze4pzsHiyN2MT8Jn4PAZNkcsOG7dhjhhQEDymH5+rMh2NJlXASERjw4SDUwn5Kx3lw9ZgJtKUuQS/xyDS2+Wwqay02Xe6gdYb7ZmyRHKIaPwGsaFebJ14Zl5EQMqFRKA7luTu6XMJeMqdAuyhzLfbJQT8Lq72djps7l1ksPH326k/mXWh9v6zvPz+k+w+tZnoYDsTRREBWReybMVX5nPEfSBzKN2EyxSSSec1wCXuIr0byARify7GdCFVMI1CZk4BImDbTpcwZLRj5radA3nDCFh2IV/NP0lB8oQq9kS43RLGFGAawkBiEM27W4rjHqBYtmLmRR1Lb//NchQBCpqKMD3x2IhVMDIJm6AKyaTidpU5bJJ2gslylWIYRYCgwPQqD2euNhPpc4ZIr6sivSrKcFLpiyeZGZzqsIkPd+esVBoTP3wdGNOFpgbdRPq66Oi57ij3uSrSX8LQsBIfsqmpnOKwSdrDxZLbScPoLgSEq8uIJWKcbW921Ia84cwmjQ1YDCWUhTVzHO1V7ZEzXnQFxrfhx1yBz871sWCmm1/U/9ZRvnr2RgLuasDm4H+7WFq7mK1rvp6pb+o6hss0QSBhZV9UagLz6Givwj2wjFDZrFKolCogtdYuU1iyMMDFzhZHbTiwiJW1T4DtJjEQ5EePb8u4kKVJTkca8HtNPG641NmWaed3BXlu7W/47sM/Y0bFXeMinYsx7sRgKyyc6WV22KQt2sHMqjCQSpkfnLUerz2HR78V4vMLlmbaXOp+n/Nd7xHwG8wNezl07pijz9qqmknJg+DmNG+ZHyRpdqNw92wfG9ZOzTNL3cz2/LWTDSs28MOvbqU6ECrQnfLaB9tp6TmVuReLQP+AhZmcwWdmzMNf5qG170NiiS5sbCTtBKo01z38zsJSBdzipT4V659YHeTd83u5/MYFdq1/gZrKrNCBZB9H2t50kE9tZKHca6J0cLH3alqUpPOh8fl9LkrqYVrIzZdWVTJl5nm++eqz9MR7M3VDyX5OXT9Q8Fkl/RaBISaGmJNC/CayPd3SG24qhagJeThw9l/sbngjUxfyhtl23++4v/ZpvGZgRLY6JoSW0hqkkBVQwnh2+g31j0ff4krX1Uy511XBytrH8bj8pXWYIvBuiQ2A8QZfYPV9Idpizfzyrd1F+Iz4KPIbFG014c/j4ZGNQpbZjV0sChViJ+k7gBIfFKr8lXjdXuLJ7nQSl7UbrQ/Qn6iv/Fd1K+rj4xGQjUIlrfjNV7dURAn4lIR2MzRE+uYmDrscJBX6BPsEyEVFz5O0d9WtqB933j3KQZY7e6P9j0DQ9PVQRpp0o3SCXlFhPypNGFaLeivO7FxePyAy8dw0JSB6MUbFol4g5+lh9KVXtEfQG6rahsgBEaNRbKvFtvQjHnkkXid1o2ZndRNlnkZKwF4sNusOVH8OVBegbIlqv4qcBr0o0GiI8Q9bpc1vxKM7Hnyvz9ni0CTRGxs5L7VNr+BbGDNNeVGVcqBLhDZF/y5iNKptX/Ibg6fjD6zt3znG7N7BHdzBHdzBpwb/BzqK/6HhZmA8AAAAAElFTkSuQmCC
16 |
--------------------------------------------------------------------------------
/qml/control/SnackBar.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | id: snackRoot
5 | width: 200 * kgScaling
6 | height: snackText.height + 24 * kgScaling
7 |
8 | property alias text: snackText.text
9 |
10 | function close() {
11 | snackText.state = "EMPTY";
12 | }
13 |
14 | Rectangle {
15 | id: snackRect
16 |
17 | anchors.left: parent.left
18 | anchors.right: parent.right
19 | anchors.bottom: parent.bottom
20 | anchors.margins: 4 * kgScaling
21 |
22 | height: snackText.height + 16 * kgScaling
23 | radius: 4 * kgScaling
24 | color: "#323232"
25 |
26 | MouseArea {
27 | anchors.fill: parent
28 | //TODO swipe close gesture
29 | onClicked: {
30 | close();
31 | }
32 | }
33 |
34 | Text {
35 | id: snackText
36 | anchors.left: parent.left
37 | anchors.right: parent.right
38 | anchors.verticalCenter: parent.verticalCenter
39 | anchors.leftMargin: 8 * kgScaling
40 | anchors.rightMargin: 8 * kgScaling
41 | color: "#FFFFFF"
42 | text: ""
43 | font.pixelSize: 12 * kgScaling
44 |
45 | wrapMode: Text.Wrap
46 | state: "EMPTY"
47 | onTextChanged: {
48 | state = snackText.text.length == 0 ? "EMPTY" : "NOT_EMPTY"
49 | }
50 |
51 | states: [
52 | State {
53 | name: "EMPTY"
54 | PropertyChanges {
55 | target: snackRect
56 | anchors.bottomMargin: -snackRoot.height
57 | }
58 | },
59 | State {
60 | name: "NOT_EMPTY"
61 | PropertyChanges {
62 | target: snackRect
63 | anchors.bottomMargin: 4 * kgScaling
64 | }
65 | }
66 | ]
67 | transitions: [
68 | Transition {
69 | NumberAnimation {
70 | properties: "anchors.bottomMargin"
71 | easing.type: Easing.InOutQuad
72 | duration: 200
73 | }
74 | },
75 | Transition {
76 | to: "EMPTY"
77 | SequentialAnimation {
78 | NumberAnimation {
79 | properties: "anchors.bottomMargin"
80 | easing.type: Easing.InOutQuad
81 | duration: 200
82 | }
83 | PropertyAction {
84 | target: snackText
85 | property: "text"
86 | value: ""
87 | }
88 | }
89 | }
90 | ]
91 |
92 | Timer {
93 | id: hideTimer
94 | interval: 5000
95 | running: snackText.state == "NOT_EMPTY"
96 | onTriggered: {
97 | close();
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | qml/Main.qml
4 | qml/control/Drawer.qml
5 | qml/control/DrawerButton.qml
6 | qml/control/LineEdit.qml
7 | qml/control/Spinner.qml
8 | qml/control/TopBar.qml
9 | qml/auth/Button.qml
10 | qml/auth/CodePage.qml
11 | qml/auth/IntroPage.qml
12 | qml/auth/PhonePage.qml
13 | qml/dialog/DialogItem.qml
14 | qml/dialog/DialogPage.qml
15 | qml/dialog/FolderItem.qml
16 | qml/message/MessageDocument.qml
17 | qml/message/MessageEdit.qml
18 | qml/message/MessageImage.qml
19 | qml/message/MessageItem.qml
20 | qml/message/MessagePage.qml
21 | img/arrow-left.png
22 | img/arrow-left_black.png
23 | img/attachment.png
24 | img/checkbox-blank-circle-outline.png
25 | img/checkbox-marked-circle.png
26 | img/delete.png
27 | img/dots-vertical.png
28 | img/loading.png
29 | img/loading_white.png
30 | img/menu.png
31 | img/send.png
32 | img/send_accent.png
33 | img/close.png
34 | img/exit-to-app.png
35 | img/refresh.png
36 | kutegramquick_big.png
37 | img/close-circle-outline_inner.png
38 | qml/control/SnackBar.qml
39 | img/media/account.png
40 | img/media/dice-multiple.png
41 | img/media/download.png
42 | img/media/file.png
43 | img/media/gamepad-square.png
44 | img/media/image.png
45 | img/media/map-marker.png
46 | img/media/poll.png
47 | img/media/receipt-text.png
48 | img/media/web.png
49 | img/media/close-circle-outline_inner.png
50 | img/share_forwarded.png
51 | img/filters/airplane.png
52 | img/filters/all.png
53 | img/filters/book.png
54 | img/filters/bots.png
55 | img/filters/cat.png
56 | img/filters/channels.png
57 | img/filters/crown.png
58 | img/filters/custom.png
59 | img/filters/edit.png
60 | img/filters/favorite.png
61 | img/filters/flower.png
62 | img/filters/game.png
63 | img/filters/groups.png
64 | img/filters/home.png
65 | img/filters/like.png
66 | img/filters/love.png
67 | img/filters/mask.png
68 | img/filters/money.png
69 | img/filters/note.png
70 | img/filters/palette.png
71 | img/filters/party.png
72 | img/filters/private.png
73 | img/filters/sport.png
74 | img/filters/study.png
75 | img/filters/trade.png
76 | img/filters/travel.png
77 | img/filters/unmuted.png
78 | img/filters/unread.png
79 | img/filters/work.png
80 | img/filters/light.png
81 | qml/control/ImageViewer.qml
82 | img/magnify-minus-outline.png
83 | img/magnify-plus-outline.png
84 | img/fullscreen.png
85 | qml/control/MainScreen.qml
86 | qml/control/AuthScreen.qml
87 | qml/control/MessageIntroPage.qml
88 | kutegramquick_small.png
89 | kutegramquick_pigler.png
90 |
91 |
92 |
--------------------------------------------------------------------------------
/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 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
50 |
51 |
52 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/messagesmodel.h:
--------------------------------------------------------------------------------
1 | #ifndef MESSAGESMODEL_H
2 | #define MESSAGESMODEL_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include "tgclient.h"
8 | #include "avatardownloader.h"
9 |
10 | class MessagesModel : public QAbstractListModel
11 | {
12 | Q_OBJECT
13 | Q_PROPERTY(QObject* client READ client WRITE setClient)
14 | Q_PROPERTY(QObject* avatarDownloader READ avatarDownloader WRITE setAvatarDownloader)
15 | Q_PROPERTY(QByteArray peer READ peer WRITE setPeer)
16 |
17 | private:
18 | QMutex _mutex;
19 | QList _history;
20 |
21 | TgClient* _client;
22 | TgLongVariant _userId;
23 |
24 | TgObject _peer;
25 | TgObject _inputPeer;
26 |
27 | TgLongVariant _upRequestId;
28 | TgLongVariant _downRequestId;
29 |
30 | qint32 _upOffset;
31 | qint32 _downOffset;
32 |
33 | AvatarDownloader* _avatarDownloader;
34 |
35 | QHash _downloadRequests;
36 |
37 | TgLongVariant _uploadId;
38 | QHash _sentMessages;
39 | TgObject _media;
40 |
41 | enum MessageRoles {
42 | PeerNameRole = Qt::UserRole + 1,
43 | MessageTextRole,
44 | MergeMessageRole,
45 | SenderNameRole,
46 | MessageTimeRole,
47 | IsChannelRole,
48 | ThumbnailColorRole,
49 | ThumbnailTextRole,
50 | AvatarRole,
51 | HasMediaRole,
52 | MediaImageRole,
53 | MediaTitleRole,
54 | MediaTextRole,
55 | MediaDownloadableRole,
56 | MessageIdRole,
57 | ForwardedFromRole,
58 | MediaUrlRole,
59 | PhotoFileRole,
60 | HasPhotoRole,
61 | PhotoSpoilerRole,
62 | MediaSpoilerRole
63 | };
64 |
65 | public:
66 | explicit MessagesModel(QObject *parent = 0);
67 | void resetState();
68 |
69 | QHash roleNames() const;
70 |
71 | void setClient(QObject *client);
72 | QObject* client() const;
73 |
74 | void setAvatarDownloader(QObject *client);
75 | QObject* avatarDownloader() const;
76 |
77 | void setPeer(QByteArray bytes);
78 | QByteArray peer() const;
79 |
80 | int rowCount(const QModelIndex& parent = QModelIndex()) const;
81 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
82 |
83 | TgObject createRow(TgObject message, TgObject sender, TgList users, TgList chats);
84 |
85 | void handleHistoryResponse(TgObject data, TgLongVariant messageId);
86 | void handleHistoryResponseUpwards(TgObject data, TgLongVariant messageId);
87 |
88 | signals:
89 | void scrollTo(qint32 index);
90 | void downloadUpdated(qint32 messageId, qint32 state, QString filePath);
91 | void draftChanged(QString draft);
92 | void uploadingProgress(qint32 progress);
93 | void scrollForNew();
94 | void sentMessageUpdate(TgObject update, TgLongVariant messageId);
95 |
96 | public slots:
97 | void authorized(TgLongVariant userId);
98 | void messagesGetHistoryResponse(TgObject data, TgLongVariant messageId);
99 | void avatarDownloaded(TgLongVariant photoId, QString filePath);
100 | void photoDownloaded(TgLongVariant photoId, QString filePath);
101 |
102 | void fileDownloaded(TgLongVariant fileId, QString filePath);
103 | void fileDownloadCanceled(TgLongVariant fileId, QString filePath);
104 |
105 | void fileUploading(TgLongVariant fileId, TgLongVariant processedLength, TgLongVariant totalLength, qint32 progressPercentage);
106 | void fileUploaded(TgLongVariant fileId, TgObject inputFile);
107 | void fileUploadCanceled(TgLongVariant fileId);
108 |
109 | void sendMessage(QString message);
110 | void uploadFile();
111 | void cancelUpload();
112 |
113 | void gotMessageUpdate(TgObject update, TgLongVariant messageId);
114 | void gotUpdate(TgObject update, TgLongVariant messageId, TgList users, TgList chats, qint32 date, qint32 seq, qint32 seqStart);
115 |
116 | bool canFetchMoreDownwards() const;
117 | void fetchMoreDownwards();
118 |
119 | bool canFetchMoreUpwards() const;
120 | void fetchMoreUpwards();
121 |
122 | void linkActivated(QString link, qint32 index);
123 | void downloadFile(qint32 index);
124 | void cancelDownload(qint32 index);
125 | };
126 |
127 | #endif // MESSAGESMODEL_H
128 |
--------------------------------------------------------------------------------
/qml/dialog/DialogItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | width: 240
5 | height: 40 * kgScaling
6 |
7 | function openDialog() {
8 | messagesModel.peer = peerBytes;
9 | topBar.peerTitle = title;
10 | topBar.peerThumbnailColor = thumbnailColor;
11 | topBar.peerThumbnailText = thumbnailText;
12 | topBar.peerAvatar = avatar;
13 | topBar.peerTooltip = tooltip;
14 | mainScreen.state = "CHAT";
15 | }
16 |
17 | MouseArea {
18 | anchors.fill: parent
19 | onClicked: {
20 | openDialog();
21 | }
22 | }
23 |
24 | property string avatarWatcher: avatar
25 | onAvatarWatcherChanged: {
26 | if (messagesModel.peer == peerBytes) {
27 | topBar.peerAvatar = avatar;
28 | }
29 | }
30 |
31 | Rectangle {
32 | id: avatarRect
33 | visible: avatar.length == 0 || avatarImage.status != Image.Ready
34 |
35 | anchors.verticalCenter: parent.verticalCenter
36 | anchors.left: parent.left
37 | anchors.leftMargin: 5 * kgScaling
38 |
39 | width: 30 * kgScaling
40 | height: width
41 | smooth: true
42 |
43 | color: thumbnailColor
44 | radius: width / 2
45 |
46 | Text {
47 | anchors.fill: parent
48 | text: thumbnailText
49 | color: "#FFFFFF"
50 | font.bold: true
51 | font.pixelSize: 12 * kgScaling
52 | horizontalAlignment: Text.AlignHCenter
53 | verticalAlignment: Text.AlignVCenter
54 | }
55 | }
56 |
57 | Image {
58 | id: avatarImage
59 | visible: avatar.length != 0
60 |
61 | anchors.left: parent.left
62 | anchors.verticalCenter: parent.verticalCenter
63 | anchors.leftMargin: avatarRect.anchors.leftMargin
64 |
65 | width: 30 * kgScaling
66 | height: width
67 | smooth: true
68 |
69 | asynchronous: true
70 | source: avatar
71 | }
72 |
73 | Column {
74 | anchors.left: avatarRect.right
75 | anchors.right: parent.right
76 | anchors.verticalCenter: avatarRect.verticalCenter
77 | anchors.leftMargin: avatarRect.anchors.leftMargin
78 | anchors.rightMargin: anchors.leftMargin
79 |
80 | Item {
81 | anchors.left: parent.left
82 | anchors.right: parent.right
83 | height: messageTimeText.height
84 |
85 | Text {
86 | text: title
87 | elide: Text.ElideRight
88 | font.pixelSize: 12 * kgScaling
89 | anchors.top: parent.top
90 | anchors.left: parent.left
91 | anchors.right: messageTimeText.left
92 | }
93 |
94 | Text {
95 | id: messageTimeText
96 | text: messageTime
97 | color: "#999999"
98 | font.pixelSize: 12 * kgScaling
99 | anchors.top: parent.top
100 | anchors.right: parent.right
101 | }
102 | }
103 |
104 | Item {
105 | anchors.left: parent.left
106 | anchors.right: parent.right
107 | height: messageSenderLabel.height
108 |
109 | Text {
110 | id: messageSenderLabel
111 | text: messageSenderName
112 | color: messageSenderColor
113 | font.pixelSize: 12 * kgScaling
114 | anchors.top: parent.top
115 | anchors.left: parent.left
116 | }
117 |
118 | Text {
119 | id: messageTextLabel
120 | text: messageText
121 | color: "#8D8D8D"
122 | elide: Text.ElideRight
123 | font.pixelSize: 12 * kgScaling
124 | anchors.top: parent.top
125 | anchors.left: messageSenderLabel.right
126 | anchors.right: parent.right
127 |
128 | onLinkActivated: {
129 | openDialog();
130 | }
131 | }
132 | }
133 | }
134 |
135 | Rectangle {
136 | height: 1 * kgScaling
137 | anchors.left: parent.left
138 | anchors.right: parent.right
139 | anchors.bottom: parent.bottom
140 | color: "#EEEEEE"
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "qmlapplicationviewer.h"
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "tgclient.h"
9 | #include "dialogsmodel.h"
10 | #include "messagesmodel.h"
11 | #include "systemname.h"
12 | #include "avatardownloader.h"
13 | #include "foldersmodel.h"
14 | #include "currentuserinfo.h"
15 | #include "platformutils.h"
16 | #include
17 | #include
18 |
19 | #if QT_VERSION >= 0x050000
20 | #include
21 | #include
22 | #else
23 | #include
24 | #endif
25 |
26 | #if QT_VERSION >= 0x040702
27 | #include
28 | #include
29 | #endif
30 |
31 | int main(int argc, char *argv[])
32 | {
33 | //TODO OpenGL acceleration
34 | //Causes some crashes on Windows, Symbian 9.2-9.3?, research it
35 | //Requires custom fonts, but they can't be install on iOS
36 | #if QT_VERSION < 0x050000
37 | //QApplication::setGraphicsSystem("opengl");
38 | #endif
39 |
40 | QApplication app(argc, argv);
41 |
42 | QSystemSemaphore sema("Kutegram_semaphone", 1);
43 | bool isRunning;
44 | sema.acquire();
45 |
46 | {
47 | QSharedMemory shmem("Kutegram_shared");
48 | shmem.attach();
49 | }
50 |
51 | QSharedMemory shmem("Kutegram_shared");
52 | if (shmem.attach())
53 | {
54 | isRunning = true;
55 | }
56 | else
57 | {
58 | shmem.create(1);
59 | isRunning = false;
60 | }
61 |
62 | sema.release();
63 | if (isRunning) {
64 | //TODO raise Kutegram window
65 | return 1;
66 | }
67 |
68 | //TODO: keypad UI navigation
69 | #ifdef Q_OS_SYMBIAN
70 | QApplication::setAttribute(Qt::AA_S60DisablePartialScreenInputMode, false);
71 | // QApplication::setNavigationMode(Qt::NavigationModeCursorAuto);
72 | #endif
73 |
74 | #if QT_VERSION >= 0x050300
75 | QApplication::setAttribute(Qt::AA_UseOpenGLES, true);
76 | #endif
77 |
78 | QApplication::setApplicationVersion(QString(VERSION).replace("\"", ""));
79 | QApplication::setApplicationName("Kutegram");
80 | QApplication::setOrganizationName("Kutegram");
81 | QApplication::setOrganizationDomain("kg.crx.moe");
82 |
83 | QTextCodec *codec = QTextCodec::codecForName("UTF-8");
84 | #if QT_VERSION < 0x050000
85 | QTextCodec::setCodecForTr(codec);
86 | QTextCodec::setCodecForCStrings(codec);
87 | #endif
88 | QTextCodec::setCodecForLocale(codec);
89 |
90 | TgClient::registerQML();
91 | qmlRegisterType("Kutegram", 1, 0, "DialogsModel");
92 | qmlRegisterType("Kutegram", 1, 0, "MessagesModel");
93 | qmlRegisterType("Kutegram", 1, 0, "AvatarDownloader");
94 | qmlRegisterType("Kutegram", 1, 0, "FoldersModel");
95 | qmlRegisterType("Kutegram", 1, 0, "CurrentUserInfo");
96 | qmlRegisterUncreatableType("Kutegram", 1, 0, "PlatformUtils", "PlatformUtils is uncreatable. Use platformUtils root property.");
97 |
98 | //TODO show status pane without button group on Symbian
99 | QmlApplicationViewer viewer;
100 | viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
101 | viewer.rootContext()->setContextProperty("kutegramVersion", QApplication::applicationVersion());
102 | viewer.rootContext()->setContextProperty("kutegramPlatform", systemName());
103 | viewer.rootContext()->setContextProperty("platformUtils", new PlatformUtils(&viewer));
104 | viewer.rootContext()->setContextProperty("kgScaling", QFontMetrics(app.font()).height() / 14.0f);
105 | viewer.setMainQmlFile(QLatin1String("qrc:///qml/Main.qml"));
106 | #if QT_VERSION >= 0x050000
107 | viewer.setTitle("Kutegram");
108 | #else
109 | viewer.setWindowTitle("Kutegram");
110 | #endif
111 | viewer.showExpanded();
112 |
113 | #if QT_VERSION >= 0x040702
114 | QNetworkConfigurationManager manager;
115 | if (manager.capabilities() & QNetworkConfigurationManager::NetworkSessionRequired) {
116 | //TODO save network selection
117 | QNetworkConfiguration config = manager.defaultConfiguration();
118 | QNetworkSession* networkSession = new QNetworkSession(config);
119 | networkSession->open(); //TODO reset network selection
120 | }
121 | #endif
122 |
123 | return app.exec();
124 | }
125 |
--------------------------------------------------------------------------------
/qml/control/MainScreen.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import "../dialog"
3 | import "../message"
4 | import "../control"
5 | import "../auth"
6 | import Kutegram 1.0
7 |
8 | Item {
9 | id: mainScreen
10 | state: "MENU"
11 |
12 | states: [
13 | State {
14 | name: "MENU"
15 | PropertyChanges {
16 | target: messagePage
17 | anchors.leftMargin: mainScreen.width
18 | }
19 | },
20 | State {
21 | name: "CHAT"
22 | PropertyChanges {
23 | target: messagePage
24 | anchors.leftMargin: mainScreen.width < 600 * kgScaling ? 0 : dialogPage.width
25 | }
26 | }
27 | ]
28 |
29 | transitions: [
30 | Transition {
31 | NumberAnimation {
32 | properties: "anchors.leftMargin"
33 | easing.type: Easing.InOutQuad
34 | duration: 200
35 | }
36 | }
37 | ]
38 |
39 | onStateChanged: {
40 | topBar.currentState = state;
41 | messagePage.globalState = "NO_SELECT";
42 | }
43 |
44 | Rectangle {
45 | color: "white"
46 | anchors.top: topBar.bottom
47 | anchors.bottom: parent.bottom
48 | anchors.left: parent.left
49 | anchors.right: parent.right
50 | }
51 |
52 | FoldersModel {
53 | id: foldersModel
54 | client: telegramClient
55 | Component.onCompleted: dialogPage.refresh.connect(refresh)
56 | }
57 |
58 | DialogsModel {
59 | id: dialogsModel
60 | folders: foldersModel
61 | client: telegramClient
62 | avatarDownloader: globalAvatarDownloader
63 | Component.onCompleted: {
64 | dialogPage.refresh.connect(refresh);
65 | dialogsModel.sendNotification.connect(platformUtils.gotNewMessage);
66 | }
67 | }
68 |
69 | MessagesModel {
70 | id: messagesModel
71 | client: telegramClient
72 | avatarDownloader: globalAvatarDownloader
73 |
74 | onScrollTo: {
75 | messagePage.messagesView.positionViewAtIndex(index, ListView.End);
76 | }
77 |
78 | onScrollForNew: {
79 | if (messagePage.messagesView.atYEnd) {
80 | messagePage.messagesView.positionViewAtIndex(messagePage.messagesView.count - 1, ListView.End);
81 | }
82 | }
83 |
84 | onDraftChanged: {
85 | messagePage.messageEdit.messageText = draft;
86 | }
87 |
88 | onUploadingProgress: {
89 | messagePage.messageEdit.uploadingProgress(progress);
90 | }
91 |
92 | Component.onCompleted: {
93 | messagesModel.sentMessageUpdate.connect(dialogsModel.gotMessageUpdate);
94 | }
95 | }
96 |
97 | DialogPage {
98 | id: dialogPage
99 | anchors.top: topBar.bottom
100 | anchors.bottom: parent.bottom
101 | anchors.left: parent.left
102 | width: parent.width < 600 * kgScaling ? parent.width : 300 * kgScaling
103 | }
104 |
105 | MessageIntroPage {
106 | id: messageIntroPage
107 | anchors.top: topBar.bottom
108 | anchors.bottom: parent.bottom
109 | anchors.left: parent.left
110 | anchors.leftMargin: dialogPage.width
111 | width: messagePage.width
112 | }
113 |
114 | MessagePage {
115 | id: messagePage
116 | anchors.top: topBar.bottom
117 | anchors.bottom: parent.bottom
118 | anchors.left: parent.left
119 | width: parent.width < 600 * kgScaling ? parent.width : parent.width - 300 * kgScaling
120 | }
121 |
122 | Rectangle {
123 | id: sidePanelSeparator
124 | anchors.top: topBar.bottom
125 | anchors.bottom: parent.bottom
126 | anchors.left: messageIntroPage.left
127 | width: 1 * kgScaling
128 | color: "#EEEEEE"
129 | }
130 |
131 | TopBar {
132 | id: topBar
133 | anchors.top: parent.top
134 | anchors.left: parent.left
135 | anchors.right: parent.right
136 | }
137 |
138 | Drawer {
139 | id: drawer
140 | anchors.top: parent.top
141 | anchors.left: parent.left
142 | anchors.right: parent.right
143 | anchors.bottom: parent.bottom
144 | }
145 |
146 | ImageViewer {
147 | id: imageViewer
148 | anchors.fill: parent
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/kutegramquick.pro:
--------------------------------------------------------------------------------
1 | TARGET = Kutegram
2 | APPNAME = Kutegram
3 | VERSION = 1.2.0
4 | PKG_VERSION = $$replace(VERSION, ".", ",")
5 | DEFINES += VERSION=\\\"$$VERSION\\\"
6 | #DATE = $$system(date /t)
7 | #DEFINES += BUILDDATE=\"\\\"$$DATE\\\"\"
8 | #COMMIT_SHA = $$system(git log --pretty=format:%h -n 1);
9 | #DEFINES += COMMIT_SHA=\"\\\"$$COMMIT_SHA\\\"\"
10 |
11 | greaterThan(QT_MAJOR_VERSION, 4) {
12 | QT += core widgets qml quick network xml
13 | win32:!winrt:QT += winextras
14 | }
15 | !greaterThan(QT_MAJOR_VERSION, 4) {
16 | QT += core declarative gui network xml
17 | }
18 |
19 | winrt {
20 | WINRT_MANIFEST.background = lightSkyBlue
21 | WINRT_MANIFEST.description = "An unofficial Telegram client, written in Qt Quick and C++."
22 | WINRT_MANIFEST.logo_large = wpassets/logo_150x150.png
23 | WINRT_MANIFEST.logo_medium = wpassets/logo_71x71.png
24 | WINRT_MANIFEST.logo_small = wpassets/logo_44x44.png
25 | WINRT_MANIFEST.logo_splash = wpassets/logo_480x800.png
26 | WINRT_MANIFEST.logo_store = wpassets/logo_store.png
27 | WINRT_MANIFEST.logo_wide = wpassets/logo_310x150.png
28 | WINRT_MANIFEST.publisherid = "CN=curoviyxru"
29 | WINRT_MANIFEST.identity = "1dcfeda8-9772-4658-8c54-853732753c6f"
30 | WINRT_MANIFEST.publisher = "curoviyxru"
31 | WINRT_MANIFEST.version = $$VERSION".0"
32 | }
33 |
34 | DEFINES += QT_USE_FAST_CONCATENATION QT_USE_FAST_OPERATOR_PLUS
35 | CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT KG_NO_DEBUG KG_NO_INFO
36 |
37 | QML_IMPORT_PATH =
38 |
39 | win32:RC_FILE = kutegramquick.rc
40 | macx:ICON = kutegramquick.icns
41 |
42 | symbian {
43 | LIBS += -lavkon -lapgrfx -lcone -leikcore -lapmime
44 |
45 | contains(SYMBIAN_VERSION, Symbian3) {
46 | DEFINES += SYMBIAN3_READY=1
47 | include(pigler/qt-library/pigler.pri)
48 | }
49 |
50 | ICON = kutegramquick.svg
51 | TARGET.UID3 = 0xE0713D51
52 | DEFINES += SYMBIAN_UID=$$TARGET.UID3
53 |
54 | TARGET.CAPABILITY += ReadUserData WriteUserData UserEnvironment NetworkServices LocalServices SwEvent
55 | #TARGET.EPOCHEAPSIZE = 0x400000 0x4000000
56 | #TARGET.EPOCSTACKSIZE = 0x14000
57 |
58 | supported_platforms = \
59 | "[0x1028315F],0,0,0,{\"S60ProductID\"}" \ # Symbian^1
60 | "[0x20022E6D],0,0,0,{\"S60ProductID\"}" \ # Symbian^3
61 | "[0x102032BE],0,0,0,{\"S60ProductID\"}" \ # Symbian 9.2
62 | "[0x102752AE],0,0,0,{\"S60ProductID\"}" \ # Symbian 9.3
63 | "[0x2003A678],0,0,0,{\"S60ProductID\"}" # Symbian Belle
64 |
65 | default_deployment.pkg_prerules -= pkg_platform_dependencies
66 | supported_platforms_deployment.pkg_prerules += supported_platforms
67 | DEPLOYMENT += supported_platforms_deployment
68 |
69 | vendor_info = \
70 | " " \
71 | "; Localised Vendor name" \
72 | "%{\"curoviyxru\"}" \
73 | " " \
74 | "; Unique Vendor name" \
75 | ":\"curoviyxru\"" \
76 | " "
77 | package.pkg_prerules += vendor_info
78 |
79 | header = "$${LITERAL_HASH}{\"Kutegram\"},(0xE0713D51),$$PKG_VERSION,TYPE=SA,RU"
80 | package.pkg_prerules += header
81 |
82 | DEPLOYMENT += package
83 | DEPLOYMENT.installer_header = "$${LITERAL_HASH}{\"Kutegram Installer\"},(0xE5E0AFB2),$$PKG_VERSION"
84 | }
85 |
86 | INCLUDEPATH += src
87 |
88 | SOURCES += \
89 | src/main.cpp \
90 | src/dialogsmodel.cpp \
91 | src/messagesmodel.cpp \
92 | src/avatardownloader.cpp \
93 | src/foldersmodel.cpp \
94 | src/currentuserinfo.cpp \
95 | src/messageutil.cpp \
96 | src/platformutils.cpp
97 |
98 | HEADERS += \
99 | src/dialogsmodel.h \
100 | src/messagesmodel.h \
101 | src/avatardownloader.h \
102 | src/foldersmodel.h \
103 | src/currentuserinfo.h \
104 | src/messageutil.h \
105 | src/platformutils.h
106 |
107 | OTHER_FILES += \
108 | qtc_packaging/debian_harmattan/rules \
109 | qtc_packaging/debian_harmattan/README \
110 | qtc_packaging/debian_harmattan/copyright \
111 | qtc_packaging/debian_harmattan/control \
112 | qtc_packaging/debian_harmattan/compat \
113 | qtc_packaging/debian_harmattan/changelog
114 |
115 | RESOURCES += \
116 | resources.qrc
117 |
118 | include(libkg/libkg.pri)
119 |
120 | include(qmlapplicationviewer/qmlapplicationviewer.pri)
121 | qtcAddDeployment()
122 |
123 | DISTFILES += \
124 | android/AndroidManifest.xml \
125 | android/gradle/wrapper/gradle-wrapper.jar \
126 | android/gradlew \
127 | android/res/values/libs.xml \
128 | android/build.gradle \
129 | android/gradle/wrapper/gradle-wrapper.properties \
130 | android/gradlew.bat
131 |
132 | ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
133 |
--------------------------------------------------------------------------------
/qmlapplicationviewer/qmlapplicationviewer.cpp:
--------------------------------------------------------------------------------
1 | // checksum 0x7f25 version 0x6000f
2 | /*
3 | This file was generated by the Qt Quick Application wizard of Qt Creator.
4 | QmlApplicationViewer is a convenience class containing mobile device specific
5 | code such as screen orientation handling. Also QML paths and debugging are
6 | handled here.
7 | It is recommended not to modify this file, since newer versions of Qt Creator
8 | may offer an updated version of it.
9 | */
10 |
11 | #include "qmlapplicationviewer.h"
12 |
13 | #include
14 | #include
15 | #include
16 | #if QT_VERSION >= 0x050000
17 | #include
18 | #include
19 | #else
20 | #include
21 | #include
22 | #include
23 | #endif
24 |
25 | #include // MEEGO_EDITION_HARMATTAN
26 |
27 | #if defined(QMLJSDEBUGGER) && QT_VERSION < 0x040800
28 |
29 | #include
30 |
31 | #if !defined(NO_JSDEBUGGER)
32 | #include
33 | #endif
34 | #if !defined(NO_QMLOBSERVER)
35 | #include
36 | #endif
37 |
38 | // Enable debugging before any QDeclarativeEngine is created
39 | struct QmlJsDebuggingEnabler
40 | {
41 | QmlJsDebuggingEnabler()
42 | {
43 | QDeclarativeDebugHelper::enableDebugging();
44 | }
45 | };
46 |
47 | // Execute code in constructor before first QDeclarativeEngine is instantiated
48 | static QmlJsDebuggingEnabler enableDebuggingHelper;
49 |
50 | #endif // QMLJSDEBUGGER
51 |
52 | class QmlApplicationViewerPrivate
53 | {
54 | QString mainQmlFile;
55 | friend class QmlApplicationViewer;
56 | static QString adjustPath(const QString &path);
57 | };
58 |
59 | QString QmlApplicationViewerPrivate::adjustPath(const QString &path)
60 | {
61 | #ifdef Q_OS_UNIX
62 | #ifdef Q_OS_MAC
63 | if (!QDir::isAbsolutePath(path))
64 | return QCoreApplication::applicationDirPath()
65 | + QLatin1String("/../Resources/") + path;
66 | #else
67 | const QString pathInInstallDir = QCoreApplication::applicationDirPath()
68 | + QLatin1String("/../") + path;
69 | if (pathInInstallDir.contains(QLatin1String("opt"))
70 | && pathInInstallDir.contains(QLatin1String("bin"))
71 | && QFileInfo(pathInInstallDir).exists()) {
72 | return pathInInstallDir;
73 | }
74 | #endif
75 | #endif
76 | return path;
77 | }
78 |
79 | #if QT_VERSION >= 0x050000
80 | QmlApplicationViewer::QmlApplicationViewer(QObject *parent) :
81 | QQuickView(dynamic_cast(parent)),
82 | #else
83 | QmlApplicationViewer::QmlApplicationViewer(QObject *parent) :
84 | QDeclarativeView(parent),
85 | #endif
86 | m_d(new QmlApplicationViewerPrivate)
87 | {
88 | connect(engine(), SIGNAL(quit()), SLOT(close()));
89 | #if QT_VERSION >= 0x050000
90 | setResizeMode(QQuickView::SizeRootObjectToView);
91 | #else
92 | setResizeMode(QDeclarativeView::SizeRootObjectToView);
93 | #endif
94 | // Qt versions prior to 4.8.0 don't have QML/JS debugging services built in
95 | #if defined(QMLJSDEBUGGER) && QT_VERSION < 0x040800
96 | #if !defined(NO_JSDEBUGGER)
97 | new QmlJSDebugger::JSDebuggerAgent(engine());
98 | #endif
99 | #if !defined(NO_QMLOBSERVER)
100 | new QmlJSDebugger::QDeclarativeViewObserver(this, this);
101 | #endif
102 | #endif
103 | }
104 |
105 | QmlApplicationViewer::~QmlApplicationViewer()
106 | {
107 | delete m_d;
108 | }
109 |
110 | void QmlApplicationViewer::setMainQmlFile(const QString &file)
111 | {
112 | m_d->mainQmlFile = file;
113 | setSource(QUrl(m_d->mainQmlFile));
114 | }
115 |
116 | void QmlApplicationViewer::addImportPath(const QString &path)
117 | {
118 | engine()->addImportPath(QmlApplicationViewerPrivate::adjustPath(path));
119 | }
120 |
121 | void QmlApplicationViewer::setOrientation(ScreenOrientation orientation)
122 | {
123 | #if defined(Q_OS_SYMBIAN)
124 | // If the version of Qt on the device is < 4.7.2, that attribute won't work
125 | if (orientation != ScreenOrientationAuto) {
126 | const QStringList v = QString::fromAscii(qVersion()).split(QLatin1Char('.'));
127 | if (v.count() == 3 && (v.at(0).toInt() << 16 | v.at(1).toInt() << 8 | v.at(2).toInt()) < 0x040702) {
128 | qWarning("Screen orientation locking only supported with Qt 4.7.2 and above");
129 | return;
130 | }
131 | }
132 |
133 | Qt::WidgetAttribute attribute;
134 | switch (orientation) {
135 | // Qt < 4.7.2 does not yet have the Qt::WA_*Orientation attributes
136 | case ScreenOrientationLockPortrait:
137 | attribute = static_cast(128);
138 | break;
139 | case ScreenOrientationLockLandscape:
140 | attribute = static_cast(129);
141 | break;
142 | default:
143 | case ScreenOrientationAuto:
144 | attribute = static_cast(130);
145 | break;
146 | };
147 | setAttribute(attribute, true);
148 | #endif // Q_OS_SYMBIAN
149 | }
150 |
151 | void QmlApplicationViewer::showExpanded()
152 | {
153 | #if defined(Q_OS_SYMBIAN) || defined(MEEGO_EDITION_HARMATTAN) || defined(Q_WS_SIMULATOR)
154 | showFullScreen();
155 | #elif defined(Q_WS_MAEMO_5)
156 | showMaximized();
157 | #else
158 | show();
159 | #endif
160 | }
161 |
--------------------------------------------------------------------------------
/qml/control/AuthScreen.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import "../dialog"
3 | import "../message"
4 | import "../control"
5 | import "../auth"
6 |
7 | Rectangle {
8 | id: authScreen
9 | property int currentIndex: 0
10 | state: "INTRO"
11 |
12 | onCurrentIndexChanged: {
13 | if (currentIndex == 0)
14 | state = "INTRO";
15 | if (currentIndex == 1)
16 | state = "PHONE";
17 | if (currentIndex == 2)
18 | state = "CODE";
19 | }
20 |
21 | property alias phonePage: phonePage
22 |
23 | states: [
24 | State {
25 | name: "INTRO"
26 | PropertyChanges {
27 | target: introPage
28 | anchors.leftMargin: 0
29 | opacity: 1
30 | }
31 | PropertyChanges {
32 | target: phonePage
33 | anchors.leftMargin: width
34 | opacity: 0
35 | }
36 | PropertyChanges {
37 | target: codePage
38 | anchors.leftMargin: width
39 | opacity: 0
40 | }
41 | },
42 | State {
43 | name: "PHONE"
44 | PropertyChanges {
45 | target: introPage
46 | anchors.leftMargin: -width
47 | opacity: 0
48 | }
49 | PropertyChanges {
50 | target: phonePage
51 | anchors.leftMargin: 0
52 | opacity: 1
53 | }
54 | PropertyChanges {
55 | target: codePage
56 | anchors.leftMargin: width
57 | opacity: 0
58 | }
59 | },
60 | State {
61 | name: "CODE"
62 | PropertyChanges {
63 | target: introPage
64 | anchors.leftMargin: -width
65 | opacity: 0
66 | }
67 | PropertyChanges {
68 | target: phonePage
69 | anchors.leftMargin: -width
70 | opacity: 0
71 | }
72 | PropertyChanges {
73 | target: codePage
74 | anchors.leftMargin: 0
75 | opacity: 1
76 | }
77 | }
78 | ]
79 |
80 | transitions: [
81 | Transition {
82 | NumberAnimation {
83 | properties: "anchors.leftMargin,opacity"
84 | easing.type: Easing.InOutQuad
85 | duration: 200
86 | }
87 | }
88 | ]
89 |
90 | IntroPage {
91 | id: introPage
92 | anchors.left: parent.left
93 | width: parent.width
94 | height: parent.height
95 | }
96 |
97 | PhonePage {
98 | id: phonePage
99 | anchors.left: parent.left
100 | width: parent.width
101 | height: parent.height
102 | }
103 |
104 | CodePage {
105 | id: codePage
106 | anchors.left: parent.left
107 | width: parent.width
108 | height: parent.height
109 | }
110 |
111 | Item {
112 | anchors.top: parent.top
113 | anchors.left: parent.left
114 | width: 40 * kgScaling
115 | height: width
116 | state: currentIndex == 0 ? "NO_BACK" : "BACK"
117 | id: authBackRect
118 |
119 | states: [
120 | State {
121 | name: "NO_BACK"
122 | PropertyChanges {
123 | target: authBackImage
124 | opacity: 0
125 | rotation: 180
126 | }
127 | },
128 | State {
129 | name: "BACK"
130 | PropertyChanges {
131 | target: authBackImage
132 | opacity: 1
133 | rotation: 0
134 | }
135 | }
136 | ]
137 |
138 | transitions: [
139 | Transition {
140 | NumberAnimation {
141 | properties: "opacity,rotation"
142 | easing.type: Easing.InOutQuad
143 | duration: 200
144 | }
145 | }
146 | ]
147 |
148 | Image {
149 | id: authBackImage
150 | anchors.centerIn: parent
151 | source: "../../img/arrow-left_black.png"
152 | width: 20 * kgScaling
153 | height: width
154 | smooth: true
155 | asynchronous: true
156 | }
157 |
158 | MouseArea {
159 | anchors.fill: parent
160 | onClicked: {
161 | setAuthProgress(false);
162 | authScreen.currentIndex = Math.max(0, authScreen.currentIndex - 1)
163 | }
164 | }
165 | }
166 |
167 | Item {
168 | anchors.top: parent.top
169 | anchors.right: parent.right
170 | width: settingsText.width + 20 * kgScaling
171 | height: 40 * kgScaling
172 | visible: false
173 |
174 | Text {
175 | id: settingsText
176 | anchors.centerIn: parent
177 | font.bold: true
178 | text: "SETTINGS"
179 | font.pixelSize: 12 * kgScaling
180 | }
181 | }
182 |
183 | Spinner {
184 | id: authSpinner
185 | visible: root.authProgress
186 | anchors.top: parent.top
187 | anchors.right: parent.right
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/qmlapplicationviewer/qmlapplicationviewer.pri:
--------------------------------------------------------------------------------
1 | # checksum 0x82af version 0x6000f
2 | # This file was generated by the Qt Quick Application wizard of Qt Creator.
3 | # The code below adds the QmlApplicationViewer to the project and handles the
4 | # activation of QML debugging.
5 | # It is recommended not to modify this file, since newer versions of Qt Creator
6 | # may offer an updated version of it.
7 |
8 | SOURCES += $$PWD/qmlapplicationviewer.cpp
9 | HEADERS += $$PWD/qmlapplicationviewer.h
10 | INCLUDEPATH += $$PWD
11 |
12 | greaterThan(QT_MAJOR_VERSION, 4) {
13 | QT += quick
14 | }
15 | !greaterThan(QT_MAJOR_VERSION, 4) {
16 | QT += declarative
17 | }
18 |
19 | # Include JS debugger library if QMLJSDEBUGGER_PATH is set
20 | !isEmpty(QMLJSDEBUGGER_PATH) {
21 | include($$QMLJSDEBUGGER_PATH/qmljsdebugger-lib.pri)
22 | } else {
23 | DEFINES -= QMLJSDEBUGGER
24 | }
25 | # This file was generated by an application wizard of Qt Creator.
26 | # The code below handles deployment to Symbian and Maemo, aswell as copying
27 | # of the application data to shadow build directories on desktop.
28 | # It is recommended not to modify this file, since newer versions of Qt Creator
29 | # may offer an updated version of it.
30 |
31 | defineTest(qtcAddDeployment) {
32 | for(deploymentfolder, DEPLOYMENTFOLDERS) {
33 | item = item$${deploymentfolder}
34 | itemsources = $${item}.sources
35 | $$itemsources = $$eval($${deploymentfolder}.source)
36 | itempath = $${item}.path
37 | $$itempath= $$eval($${deploymentfolder}.target)
38 | export($$itemsources)
39 | export($$itempath)
40 | DEPLOYMENT += $$item
41 | }
42 |
43 | MAINPROFILEPWD = $$PWD
44 |
45 | symbian {
46 | isEmpty(ICON):exists($${TARGET}.svg):ICON = $${TARGET}.svg
47 | isEmpty(TARGET.EPOCHEAPSIZE):TARGET.EPOCHEAPSIZE = 0x20000 0x2000000
48 | } else:win32 {
49 | copyCommand =
50 | for(deploymentfolder, DEPLOYMENTFOLDERS) {
51 | source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source)
52 | source = $$replace(source, /, \\)
53 | sourcePathSegments = $$split(source, \\)
54 | target = $$OUT_PWD/$$eval($${deploymentfolder}.target)/$$last(sourcePathSegments)
55 | target = $$replace(target, /, \\)
56 | !isEqual(source,$$target) {
57 | !isEmpty(copyCommand):copyCommand += &&
58 | isEqual(QMAKE_DIR_SEP, \\) {
59 | copyCommand += $(COPY_DIR) \"$$source\" \"$$target\"
60 | } else {
61 | source = $$replace(source, \\\\, /)
62 | target = $$OUT_PWD/$$eval($${deploymentfolder}.target)
63 | target = $$replace(target, \\\\, /)
64 | copyCommand += test -d \"$$target\" || mkdir -p \"$$target\" && cp -r \"$$source\" \"$$target\"
65 | }
66 | }
67 | }
68 | !isEmpty(copyCommand) {
69 | copyCommand = @echo Copying application data... && $$copyCommand
70 | copydeploymentfolders.commands = $$copyCommand
71 | first.depends = $(first) copydeploymentfolders
72 | export(first.depends)
73 | export(copydeploymentfolders.commands)
74 | QMAKE_EXTRA_TARGETS += first copydeploymentfolders
75 | }
76 | } else:unix {
77 | maemo5 {
78 | desktopfile.files = $${TARGET}.desktop
79 | desktopfile.path = /usr/share/applications/hildon
80 | icon.files = $${TARGET}64.png
81 | icon.path = /usr/share/icons/hicolor/64x64/apps
82 | } else:!isEmpty(MEEGO_VERSION_MAJOR) {
83 | desktopfile.files = $${TARGET}_harmattan.desktop
84 | desktopfile.path = /usr/share/applications
85 | icon.files = $${TARGET}80.png
86 | icon.path = /usr/share/icons/hicolor/80x80/apps
87 | } else { # Assumed to be a Desktop Unix
88 | copyCommand =
89 | for(deploymentfolder, DEPLOYMENTFOLDERS) {
90 | source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source)
91 | source = $$replace(source, \\\\, /)
92 | macx {
93 | target = $$OUT_PWD/$${TARGET}.app/Contents/Resources/$$eval($${deploymentfolder}.target)
94 | } else {
95 | target = $$OUT_PWD/$$eval($${deploymentfolder}.target)
96 | }
97 | target = $$replace(target, \\\\, /)
98 | sourcePathSegments = $$split(source, /)
99 | targetFullPath = $$target/$$last(sourcePathSegments)
100 | !isEqual(source,$$targetFullPath) {
101 | !isEmpty(copyCommand):copyCommand += &&
102 | copyCommand += $(MKDIR) \"$$target\"
103 | copyCommand += && $(COPY_DIR) \"$$source\" \"$$target\"
104 | }
105 | }
106 | !isEmpty(copyCommand) {
107 | copyCommand = @echo Copying application data... && $$copyCommand
108 | copydeploymentfolders.commands = $$copyCommand
109 | first.depends = $(first) copydeploymentfolders
110 | export(first.depends)
111 | export(copydeploymentfolders.commands)
112 | QMAKE_EXTRA_TARGETS += first copydeploymentfolders
113 | }
114 | }
115 | installPrefix = /opt/$${TARGET}
116 | for(deploymentfolder, DEPLOYMENTFOLDERS) {
117 | item = item$${deploymentfolder}
118 | itemfiles = $${item}.files
119 | $$itemfiles = $$eval($${deploymentfolder}.source)
120 | itempath = $${item}.path
121 | $$itempath = $${installPrefix}/$$eval($${deploymentfolder}.target)
122 | export($$itemfiles)
123 | export($$itempath)
124 | INSTALLS += $$item
125 | }
126 |
127 | !isEmpty(desktopfile.path) {
128 | export(icon.files)
129 | export(icon.path)
130 | export(desktopfile.files)
131 | export(desktopfile.path)
132 | INSTALLS += icon desktopfile
133 | }
134 |
135 | target.path = $${installPrefix}/bin
136 | export(target.path)
137 | INSTALLS += target
138 | }
139 |
140 | export (ICON)
141 | export (INSTALLS)
142 | export (DEPLOYMENT)
143 | export (TARGET.EPOCHEAPSIZE)
144 | export (TARGET.CAPABILITY)
145 | export (LIBS)
146 | export (QMAKE_EXTRA_TARGETS)
147 | }
148 |
--------------------------------------------------------------------------------
/qml/message/MessageItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | property alias currentState: messageRoot.state
5 | property int messageIndex: index
6 |
7 | id: messageRoot
8 | width: ListView.view.width
9 | height: Math.max(textContainer.height, avatarImage.height) + 6 * kgScaling
10 | state: "NO_SELECT"
11 |
12 | states: [
13 | State {
14 | name: "NO_SELECT"
15 | PropertyChanges {
16 | target: checkbox
17 | opacity: 0
18 | x: -15 * kgScaling
19 | }
20 | },
21 | State {
22 | name: "SHOW_SELECT"
23 | PropertyChanges {
24 | target: checkbox
25 | opacity: 1
26 | x: 5 * kgScaling
27 | }
28 | }
29 | ]
30 |
31 | transitions: [
32 | Transition {
33 | NumberAnimation {
34 | properties: "x,opacity"
35 | easing.type: Easing.InOutQuad
36 | duration: 200
37 | }
38 | }
39 | ]
40 |
41 | // MouseArea {
42 | // anchors.fill: parent
43 | // onClicked: {
44 | // if (ListView.view.parent.globalState == "SHOW_SELECT")
45 | // ListView.view.parent.globalState = "NO_SELECT"
46 | // else ListView.view.parent.globalState = "SHOW_SELECT"
47 | // }
48 | // }
49 |
50 | Image {
51 | id: checkbox
52 | source: "../../img/checkbox-blank-circle-outline.png"
53 | smooth: true
54 | width: 20 * kgScaling
55 | height: width
56 | x: 5 * kgScaling
57 | y: 3 * kgScaling
58 | asynchronous: true
59 | }
60 |
61 | Rectangle {
62 | id: messageAvatar
63 | visible: !mergeMessage && !isChannel && (avatar.length == 0 || avatarImage.status != Image.Ready)
64 |
65 | anchors.top: parent.top
66 | anchors.topMargin: 3 * kgScaling
67 | anchors.left: checkbox.right
68 | anchors.leftMargin: (isChannel ? -35 : 5) * kgScaling
69 |
70 | width: 30 * kgScaling
71 | height: visible ? width : 0
72 | smooth: true
73 |
74 | color: thumbnailColor
75 | radius: width / 2
76 |
77 | Text {
78 | anchors.fill: parent
79 | text: thumbnailText
80 | color: "#FFFFFF"
81 | font.bold: true
82 | font.pixelSize: 12 * kgScaling
83 | horizontalAlignment: Text.AlignHCenter
84 | verticalAlignment: Text.AlignVCenter
85 | }
86 | }
87 |
88 | Image {
89 | id: avatarImage
90 | visible: !mergeMessage && !isChannel && avatar.length != 0
91 |
92 | anchors.top: parent.top
93 | anchors.topMargin: 3 * kgScaling
94 | anchors.left: checkbox.right
95 | anchors.leftMargin: (isChannel ? -35 : 5) * kgScaling
96 |
97 | width: 30 * kgScaling
98 | height: visible ? width : 0
99 | smooth: true
100 |
101 | asynchronous: true
102 | source: avatar
103 | }
104 |
105 | Column {
106 | id: textContainer
107 | anchors.top: parent.top
108 | anchors.left: messageAvatar.right
109 | anchors.right: parent.right
110 | anchors.topMargin: 3 * kgScaling
111 | anchors.leftMargin: 5 * kgScaling
112 | anchors.rightMargin: 5 * kgScaling
113 | spacing: 2 * kgScaling
114 |
115 | Row {
116 | anchors.left: parent.left
117 | anchors.right: parent.right
118 | spacing: 4 * kgScaling
119 |
120 | Text {
121 | text: senderName
122 | font.bold: true
123 | font.pixelSize: 12 * kgScaling
124 | visible: !mergeMessage
125 | }
126 |
127 | Text {
128 | anchors.bottom: parent.bottom
129 | text: messageTime
130 | color: "#999999"
131 | visible: !mergeMessage
132 | font.pixelSize: 12 * kgScaling
133 | }
134 | }
135 |
136 | Row {
137 | anchors.left: parent.left
138 | anchors.right: parent.right
139 | spacing: 4 * kgScaling
140 | visible: forwardedFrom.length != 0
141 |
142 | Image {
143 | source: "../../img/share_forwarded.png"
144 | smooth: true
145 | width: 20 * kgScaling
146 | height: width
147 | asynchronous: true
148 | }
149 |
150 | Text {
151 | text: forwardedFrom
152 | font.bold: true
153 | font.pixelSize: 12 * kgScaling
154 | color: "#8D8D8D"
155 | anchors.verticalCenter: parent.verticalCenter
156 | }
157 | }
158 |
159 | Text {
160 | anchors.left: parent.left
161 | anchors.right: parent.right
162 |
163 | wrapMode: Text.Wrap
164 | text: messageText
165 | visible: messageText.length != 0
166 | color: "#000000"
167 | font.pixelSize: 12 * kgScaling
168 |
169 | onLinkActivated: {
170 | messagesModel.linkActivated(link, index);
171 | }
172 | }
173 |
174 | Repeater {
175 | model: hasPhoto
176 | MessageImage {
177 | state: currentState
178 | anchors.left: parent.left
179 | width: Math.min(280, parent.width)
180 | }
181 | }
182 |
183 | Repeater {
184 | model: hasMedia
185 | MessageDocument {
186 | rowIndex: messageIndex
187 | anchors.left: parent.left
188 | anchors.right: parent.right
189 | state: currentState
190 | }
191 | }
192 | }
193 |
194 | // Rectangle {
195 | // id: debugRectangle
196 | // anchors.fill: parent
197 | // color: Qt.hsla((messageId % 37) / 36, 0.5, 0.5, 0.8)
198 | // }
199 | }
200 |
--------------------------------------------------------------------------------
/qml/message/MessageDocument.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import "../control"
3 |
4 | Rectangle {
5 | width: 240 * kgScaling
6 | height: 40 * kgScaling
7 |
8 | property int rowIndex: -1
9 | property string filePath: ""
10 |
11 | Component.onCompleted: {
12 | messagesModel.downloadUpdated.connect(handleDownload);
13 | }
14 |
15 | function handleDownload(mid, state, path) {
16 | if (mid != messageId) {
17 | return;
18 | }
19 |
20 | filePath = path;
21 |
22 | switch (state) {
23 | case 1:
24 | attachButton.state = "DOWNLOADED";
25 | break;
26 | case 0:
27 | attachButton.state = "DOWNLOADING";
28 | break;
29 | case -1:
30 | attachButton.state = "NOT_DOWNLOADING";
31 | break;
32 | }
33 | }
34 |
35 | Rectangle {
36 | anchors.left: parent.left
37 | anchors.verticalCenter: parent.verticalCenter
38 | anchors.leftMargin: 5 * kgScaling
39 | id: attachButton
40 |
41 | width: 30 * kgScaling
42 | height: width
43 | radius: width / 2
44 | smooth: true
45 | color: globalAccent
46 |
47 | state: mediaDownloadable ? "NOT_DOWNLOADING" : "DOWNLOADED"
48 |
49 | Image {
50 | id: downloadImage
51 | anchors.centerIn: parent
52 | width: 20 * kgScaling
53 | height: width
54 | smooth: true
55 | source: "../../img/media/download.png"
56 | asynchronous: true
57 | }
58 |
59 | Image {
60 | id: documentImage
61 | anchors.centerIn: parent
62 | width: 20 * kgScaling
63 | height: width
64 | smooth: true
65 | asynchronous: true
66 | source: mediaImage
67 | }
68 |
69 | Spinner {
70 | id: uploadSpinner
71 | anchors.centerIn: parent
72 | white: true
73 |
74 | Image {
75 | anchors.centerIn: parent
76 | width: 20 * kgScaling
77 | height: width
78 | smooth: true
79 | source: "../../img/media/close-circle-outline_inner.png"
80 | asynchronous: true
81 | }
82 | }
83 |
84 | states: [
85 | State {
86 | name: "DOWNLOADING"
87 | PropertyChanges {
88 | target: downloadImage
89 | opacity: 0
90 | scale: 0
91 | }
92 | PropertyChanges {
93 | target: documentImage
94 | opacity: 0
95 | scale: 0
96 | }
97 | PropertyChanges {
98 | target: uploadSpinner
99 | opacity: 1
100 | scale: 1
101 | }
102 | },
103 | State {
104 | name: "NOT_DOWNLOADING"
105 | PropertyChanges {
106 | target: downloadImage
107 | opacity: 1
108 | scale: 1
109 | }
110 | PropertyChanges {
111 | target: documentImage
112 | opacity: 0
113 | scale: 0
114 | }
115 | PropertyChanges {
116 | target: uploadSpinner
117 | opacity: 0
118 | scale: 0
119 | }
120 | },
121 | State {
122 | name: "DOWNLOADED"
123 | PropertyChanges {
124 | target: downloadImage
125 | opacity: 0
126 | scale: 0
127 | }
128 | PropertyChanges {
129 | target: documentImage
130 | opacity: 1
131 | scale: 1
132 | }
133 | PropertyChanges {
134 | target: uploadSpinner
135 | opacity: 0
136 | scale: 0
137 | }
138 | }
139 | ]
140 |
141 | transitions: [
142 | Transition {
143 | NumberAnimation {
144 | properties: "opacity,scale"
145 | easing.type: Easing.InOutQuad
146 | duration: 200
147 | }
148 | }
149 | ]
150 | }
151 |
152 | Column {
153 | anchors.left: attachButton.right
154 | anchors.verticalCenter: attachButton.verticalCenter
155 | anchors.leftMargin: attachButton.anchors.leftMargin
156 |
157 | Row {
158 | spacing: 5 * kgScaling
159 | Text {
160 | text: mediaTitle
161 | font.bold: true
162 | font.pixelSize: 12 * kgScaling
163 | }
164 | }
165 |
166 | Text {
167 | text: mediaText
168 | color: "#8D8D8D"
169 | font.pixelSize: 12 * kgScaling
170 | }
171 | }
172 |
173 | Rectangle {
174 | id: spoilerRect
175 | visible: mediaSpoiler
176 | anchors.fill: parent
177 | color: "gray"
178 |
179 | Text {
180 | anchors.fill: parent
181 | verticalAlignment: Text.AlignVCenter
182 | horizontalAlignment: Text.AlignHCenter
183 | font.pixelSize: 12 * kgScaling
184 | color: "white"
185 | text: "Media is hidden.\nClick to reveal."
186 | }
187 | }
188 |
189 | MouseArea {
190 | anchors.fill: parent
191 | enabled: spoilerRect.visible || mediaDownloadable || mediaUrl.length != 0
192 | onClicked: {
193 | if (spoilerRect.visible) {
194 | spoilerRect.visible = false;
195 | return;
196 | }
197 |
198 | if (mediaUrl.length != 0) {
199 | messagesModel.openUrl(mediaUrl);
200 | return;
201 | }
202 |
203 | if (attachButton.state == "NOT_DOWNLOADING") {
204 | messagesModel.downloadFile(rowIndex);
205 | } else if (attachButton.state == "DOWNLOADED" && filePath.length != 0) {
206 | messagesModel.openUrl(filePath);
207 | } else {
208 | messagesModel.cancelDownload(rowIndex);
209 | }
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/avatardownloader.cpp:
--------------------------------------------------------------------------------
1 | #include "avatardownloader.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | AvatarDownloader::AvatarDownloader(QObject *parent)
9 | : QObject(parent)
10 | , _mutex(QMutex::Recursive)
11 | , _client(0)
12 | , _userId(0)
13 | , _requestsAvatars()
14 | , _requestsPhotos()
15 | , _downloadedAvatars()
16 | , _downloadedPhotos()
17 | {
18 | }
19 |
20 | void AvatarDownloader::saveDatabase()
21 | {
22 | if (!_client) {
23 | return;
24 | }
25 |
26 | QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName() + "_cache");
27 | settings.setValue("DownloadedAvatars", _downloadedAvatars);
28 | settings.setValue("DownloadedPhotos", _downloadedPhotos);
29 | }
30 |
31 | void AvatarDownloader::readDatabase()
32 | {
33 | if (!_client) {
34 | return;
35 | }
36 |
37 | QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName() + "_cache");
38 | _downloadedAvatars = settings.value("DownloadedAvatars").toList();
39 | _downloadedPhotos = settings.value("DownloadedPhotos").toList();
40 | }
41 |
42 | void AvatarDownloader::setClient(QObject *client)
43 | {
44 | QMutexLocker lock(&_mutex);
45 |
46 | if (_client) {
47 | _client->disconnect(this);
48 | saveDatabase();
49 | }
50 |
51 | _client = dynamic_cast(client);
52 | _userId = _client->getUserId();
53 |
54 | _requestsAvatars.clear();
55 | _requestsPhotos.clear();
56 | readDatabase();
57 |
58 | if (!_client) return;
59 |
60 | _client->sessionDirectory().mkdir("Kutegram_avatars");
61 | _client->sessionDirectory().mkdir("Kutegram_photos");
62 |
63 | connect(_client, SIGNAL(authorized(TgLongVariant)), this, SLOT(authorized(TgLongVariant)));
64 | connect(_client, SIGNAL(fileDownloaded(TgLongVariant,QString)), this, SLOT(fileDownloaded(TgLongVariant,QString)));
65 | connect(_client, SIGNAL(fileDownloadCanceled(TgLongVariant,QString)), this, SLOT(fileDownloadCanceled(TgLongVariant,QString)));
66 | }
67 |
68 | void AvatarDownloader::authorized(TgLongVariant userId)
69 | {
70 | QMutexLocker lock(&_mutex);
71 |
72 | if (_userId != userId) {
73 | _requestsAvatars.clear();
74 | _requestsPhotos.clear();
75 | _userId = userId;
76 | }
77 | }
78 |
79 | QObject* AvatarDownloader::client() const
80 | {
81 | return _client;
82 | }
83 |
84 | qint64 AvatarDownloader::downloadPhoto(TgObject photo)
85 | {
86 | QMutexLocker lock(&_mutex);
87 |
88 | if (!_client || !_client->isAuthorized() || GETID(photo) == 0) {
89 | return 0;
90 | }
91 |
92 | qint64 photoId = photo["id"].toLongLong();
93 |
94 | QString relativePath = "Kutegram_photos/" + QString::number(photoId) + ".jpg";
95 | QString avatarFilePath = _client->sessionDirectory().absoluteFilePath(relativePath);
96 |
97 | if (!_downloadedPhotos.contains(photoId)) {
98 | qint64 loadingId = _client->downloadFile(avatarFilePath, photo).toLongLong();
99 | _requestsPhotos[loadingId] = photoId;
100 | } else {
101 | #if QT_VERSION >= 0x050000
102 | emit photoDownloaded(photoId, "file:///" + avatarFilePath);
103 | #else
104 | emit photoDownloaded(photoId, avatarFilePath);
105 | #endif
106 | }
107 |
108 | return photoId;
109 | }
110 |
111 | qint64 AvatarDownloader::downloadAvatar(TgObject peer)
112 | {
113 | QMutexLocker lock(&_mutex);
114 |
115 | if (!_client || !_client->isAuthorized() || TgClient::commonPeerType(peer) == 0) {
116 | return 0;
117 | }
118 |
119 | TgObject photo = peer["photo"].toMap();
120 | if (GETID(photo) == 0) {
121 | return 0;
122 | }
123 |
124 | qint64 photoId = photo["photo_id"].toLongLong();
125 |
126 | QString relativePath = "Kutegram_avatars/" + QString::number(photoId) + ".jpg";
127 | QString avatarFilePath = _client->sessionDirectory().absoluteFilePath(relativePath);
128 |
129 | if (!_downloadedAvatars.contains(photoId)) {
130 | qint64 loadingId = _client->downloadFile(avatarFilePath, peer).toLongLong();
131 | _requestsAvatars[loadingId] = photoId;
132 | } else {
133 | #if QT_VERSION >= 0x050000
134 | emit avatarDownloaded(photoId, "file:///" + avatarFilePath + ".png");
135 | #else
136 | emit avatarDownloaded(photoId, avatarFilePath + ".png");
137 | #endif
138 | }
139 |
140 | return photoId;
141 | }
142 |
143 | void AvatarDownloader::fileDownloaded(TgLongVariant fileId, QString filePath)
144 | {
145 | QMutexLocker lock(&_mutex);
146 |
147 | TgLongVariant photoId = _requestsAvatars.take(fileId.toLongLong());
148 | if (!photoId.isNull()) {
149 | QFile file(filePath);
150 | if (!file.open(QFile::ReadOnly)) {
151 | return;
152 | }
153 |
154 | QImage roundedImage(160, 160, QImage::Format_ARGB32);
155 | roundedImage.fill(Qt::transparent);
156 | QPainter painter(&roundedImage);
157 | painter.setRenderHint(QPainter::Antialiasing);
158 | painter.setBrush(QBrush(QImage::fromData(file.readAll())));
159 | painter.setPen(Qt::transparent);
160 | file.close();
161 | painter.drawRoundedRect(0, 0, 160, 160, 80, 80);
162 | filePath += ".png";
163 | if (!roundedImage.save(filePath)) {
164 | return;
165 | }
166 |
167 | _downloadedAvatars.append(photoId);
168 | saveDatabase();
169 | #if QT_VERSION >= 0x050000
170 | emit avatarDownloaded(photoId, "file:///" + filePath);
171 | #else
172 | emit avatarDownloaded(photoId, filePath);
173 | #endif
174 | return;
175 | }
176 |
177 | photoId = _requestsPhotos.take(fileId.toLongLong());
178 | if (!photoId.isNull()) {
179 | QFile file(filePath);
180 | if (!file.open(QFile::ReadOnly)) {
181 | return;
182 | }
183 |
184 | QImage scaledImage = QImage::fromData(file.readAll());
185 | if (scaledImage.height() > scaledImage.width()) {
186 | scaledImage = scaledImage.scaledToHeight(280, Qt::SmoothTransformation);
187 | } else {
188 | scaledImage = scaledImage.scaledToWidth(280, Qt::SmoothTransformation);
189 | }
190 | file.close();
191 | if (!scaledImage.save(filePath + ".thumbnail.jpg")) {
192 | return;
193 | }
194 |
195 | _downloadedPhotos.append(photoId);
196 | saveDatabase();
197 | #if QT_VERSION >= 0x050000
198 | emit photoDownloaded(photoId, "file:///" + filePath);
199 | #else
200 | emit photoDownloaded(photoId, filePath);
201 | #endif
202 | return;
203 | }
204 | }
205 |
206 | void AvatarDownloader::fileDownloadCanceled(TgLongVariant fileId, QString filePath)
207 | {
208 | QMutexLocker lock(&_mutex);
209 |
210 | _requestsAvatars.remove(fileId.toLongLong());
211 | _requestsPhotos.remove(fileId.toLongLong());
212 | }
213 |
214 | QString AvatarDownloader::getAvatarText(QString title)
215 | {
216 | QStringList split = title.split(" ", QString::SkipEmptyParts);
217 | QString result;
218 |
219 | for (qint32 i = 0; i < split.size(); ++i) {
220 | QString item = split[i];
221 | for (qint32 j = 0; j < item.length(); ++j) {
222 | if (item[j].isLetterOrNumber()) {
223 | result += item[j].toUpper();
224 | break;
225 | }
226 | }
227 |
228 | if (result.size() > 1) {
229 | break;
230 | }
231 | }
232 |
233 | if (result.isEmpty() && !title.isEmpty())
234 | result += title[0].toUpper();
235 |
236 | return result;
237 | }
238 |
239 | QColor AvatarDownloader::userColor(TgLongVariant id)
240 | {
241 | return QColor::fromHsl(id.toLongLong() % 360, 160, 120);
242 | }
243 |
--------------------------------------------------------------------------------
/qml/main.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import "dialog"
3 | import "message"
4 | import "control"
5 | import "auth"
6 | import Kutegram 1.0
7 |
8 | Item {
9 | //TODO: remove dynamically unused pages / components from memory
10 | //TODO: keypad navigation
11 | id: root
12 |
13 | width: 320
14 | height: 240
15 |
16 | property color globalAccent: platformUtils.isWindows() ? platformUtils.windowsRealColorizationColor() : "#54759E"
17 |
18 | state: "AUTH"
19 |
20 | states: [
21 | State {
22 | name: "AUTH"
23 | PropertyChanges {
24 | target: mainScreen
25 | anchors.leftMargin: -root.width
26 | opacity: 0
27 | }
28 | PropertyChanges {
29 | target: authScreen
30 | anchors.leftMargin: 0
31 | }
32 | },
33 | State {
34 | name: "MAIN"
35 | PropertyChanges {
36 | target: mainScreen
37 | anchors.leftMargin: 0
38 | opacity: 1
39 | }
40 | PropertyChanges {
41 | target: authScreen
42 | anchors.leftMargin: -root.width
43 | }
44 | }
45 | ]
46 |
47 | transitions: [
48 | Transition {
49 | NumberAnimation {
50 | properties: "anchors.leftMargin,opacity"
51 | easing.type: Easing.InOutQuad
52 | duration: 200
53 | }
54 | }
55 | ]
56 |
57 | property int currentFolderIndex: 0
58 | property bool authProgress: false
59 |
60 | function setAuthProgress(visible) {
61 | authProgress = visible;
62 | }
63 |
64 | Component.onCompleted: {
65 | if (telegramClient.hasSession()) {
66 | setAuthProgress(true);
67 | telegramClient.start();
68 | }
69 | }
70 |
71 | TgClient {
72 | id: telegramClient
73 |
74 | onInitialized: {
75 | if (hasUserId) {
76 | return;
77 | }
78 |
79 | setAuthProgress(false);
80 | authScreen.currentIndex = 1;
81 |
82 | helpGetCountriesList();
83 | }
84 |
85 | onDisconnected: {
86 | setAuthProgress(false);
87 |
88 | if (!hasUserId) {
89 | root.state = "AUTH";
90 | authScreen.currentIndex = 0;
91 | } else {
92 | // TODO: show reconnecting
93 | // TODO: timer!
94 | telegramClient.start();
95 | }
96 | }
97 |
98 | onAuthSentCodeResponse: {
99 | setAuthProgress(false);
100 |
101 | authScreen.phonePage.phoneCodeHash = data["phone_code_hash"];
102 | switch (data["type"]["_"]) {
103 | //TODO messages
104 | // case TLType::AuthSentCodeTypeApp:
105 | // codeNumberDescriptionLabel->setText("A code was sent via Telegram to your other\ndevices, if you have any connected.");
106 | // break;
107 | // case TLType::AuthSentCodeTypeSms:
108 | // codeNumberDescriptionLabel->setText("We've sent an activation code to your phone.\nPlease enter it below.");
109 | // break;
110 | // case TLType::AuthSentCodeTypeCall:
111 | // break;
112 | // case TLType::AuthSentCodeTypeFlashCall:
113 | // break;
114 | // case TLType::AuthSentCodeTypeMissedCall:
115 | // break;
116 | // case TLType::AuthSentCodeTypeEmailCode:
117 | // break;
118 | // case TLType::AuthSentCodeTypeSetUpEmailRequired:
119 | // //TODO: show error or implement it lol
120 | // break;
121 | // case TLType::AuthSentCodeTypeFragmentSms:
122 | // break;
123 | // case TLType::AuthSentCodeTypeFirebaseSms:
124 | // break;
125 | }
126 |
127 | authScreen.currentIndex = 2;
128 | }
129 |
130 | onAuthAuthorizationResponse: {
131 | setAuthProgress(false);
132 |
133 | if (data["_"] == 0x44747e9a) {
134 | //TODO sign up / registration support
135 | snackBar.text = "Sign up isn't supported now. Please, use official app for signing up.";
136 | }
137 | }
138 |
139 | onAuthorized: {
140 | setAuthProgress(false);
141 | //TODO hide reconnecting
142 |
143 | root.state = "MAIN";
144 | }
145 |
146 | onRpcError: {
147 | setAuthProgress(false);
148 |
149 | //TODO think how to improve it
150 | if (errorMessage == "PHONE_NUMBER_INVALID") {
151 | snackBar.text = "Invalid phone number. Please try again.";
152 | }
153 | else if (errorMessage == "PHONE_NUMBER_FLOOD") {
154 | snackBar.text = "Phone is used too many times recently.";
155 | }
156 | else if (errorMessage == "PHONE_CODE_INVALID") {
157 | snackBar.text = "You have entered an invalid code.";
158 | }
159 | else {
160 | snackBar.text = "RPC error occured: " + errorMessage + " (" + errorCode + ")"
161 | }
162 | }
163 |
164 | onSocketError: {
165 | if (state == "AUTH") {
166 | setAuthProgress(false);
167 | snackBar.text = "Socket error occured: " + errorMessage + " (" + errorCode + ")"
168 | } else {
169 | snackBar.text = "Reconnecting...";
170 | }
171 | }
172 |
173 | onTfaRequired: {
174 | setAuthProgress(false);
175 |
176 | //TODO 2fa support
177 | snackBar.text = "2FA isn't supported now. You can disable 2FA, log in and enable it afterwards.";
178 | }
179 |
180 | onHelpCountriesListResponse: {
181 | //TODO country selector
182 | }
183 |
184 | //Debug only
185 | // onFileDownloadCanceled: {
186 | // console.log("[INFO] File " + fileId + " download canceled");
187 | // }
188 |
189 | // onFileDownloaded: {
190 | // console.log("[INFO] File " + fileId + " have been downloaded");
191 | // }
192 |
193 | // onFileDownloading: {
194 | // console.log("[INFO] File " + fileId + " download progress: " + processedLength + " / " + totalLength + " " + progressPercentage + " %");
195 | // }
196 |
197 | // onFileUploadCanceled: {
198 | // console.log("[INFO] File " + fileId + " upload canceled");
199 | // }
200 |
201 | // onFileUploaded: {
202 | // console.log("[INFO] File " + fileId + " have been uploaded");
203 | // }
204 |
205 | // onFileUploading: {
206 | // console.log("[INFO] File " + fileId + " upload progress: " + processedLength + " / " + totalLength + " " + progressPercentage + " %");
207 | // }
208 | }
209 |
210 | AvatarDownloader {
211 | id: globalAvatarDownloader
212 | client: telegramClient
213 | }
214 |
215 | AuthScreen {
216 | id: authScreen
217 | anchors.left: parent.left
218 | anchors.top: parent.top
219 | anchors.bottom: parent.bottom
220 | width: parent.width
221 | }
222 |
223 | MainScreen {
224 | id: mainScreen
225 | anchors.left: parent.left
226 | anchors.top: parent.top
227 | anchors.bottom: parent.bottom
228 | width: parent.width
229 | }
230 |
231 | SnackBar {
232 | id: snackBar
233 | anchors.left: parent.left
234 | anchors.right: parent.right
235 | anchors.bottom: parent.bottom
236 | }
237 |
238 | Keys.onPressed: {
239 | if (event.key == Qt.Key_Context1 || event.key == Qt.Key_Escape) {
240 | topBar.menuButtonClicked();
241 | }
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/platformutils.cpp:
--------------------------------------------------------------------------------
1 | #include "platformutils.h"
2 |
3 | #include
4 | #include "debug.h"
5 |
6 | #if defined(Q_OS_WIN32) && QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
7 | #include
8 | #define DWM_FEATURES
9 | #endif
10 |
11 | #ifdef SYMBIAN3_READY
12 | #include
13 | #endif
14 |
15 | #ifdef Q_OS_SYMBIAN
16 | #include
17 | #include
18 | #include
19 | #else
20 | #include
21 | #endif
22 |
23 | #include
24 |
25 | PlatformUtils::PlatformUtils(QObject *parent)
26 | : QObject(parent)
27 | , window(dynamic_cast(parent))
28 | #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINPHONE)
29 | , trayIcon(this)
30 | , trayMenu()
31 | #endif
32 | , unread()
33 | #ifdef SYMBIAN3_READY
34 | , pigler()
35 | , piglerId(-1)
36 | #endif
37 | {
38 | if (window) {
39 | window->setAttribute(Qt::WA_DeleteOnClose, false);
40 | window->setAttribute(Qt::WA_QuitOnClose, false);
41 | }
42 |
43 | #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINPHONE)
44 | connect(&trayIcon, SIGNAL(messageClicked()), this, SLOT(messageClicked()));
45 | connect(&trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));
46 | connect(&trayMenu, SIGNAL(triggered(QAction*)), this, SLOT(menuTriggered(QAction*)));
47 |
48 | trayMenu.addAction("Open Kutegram");
49 | trayMenu.addAction("Exit");
50 |
51 | trayIcon.setContextMenu(&trayMenu);
52 | trayIcon.setIcon(QIcon(":/kutegramquick_small.png"));
53 | trayIcon.setToolTip("Kutegram");
54 | trayIcon.show();
55 | #endif
56 |
57 | #ifdef SYMBIAN3_READY
58 | qint32 response = pigler.init("Kutegram"); //TODO think about randomization
59 | if (response >= 0) {
60 | if (response > 0)
61 | piglerHandleTap(response);
62 |
63 | connect(&pigler, SIGNAL(handleTap(qint32)), this, SLOT(piglerHandleTap(qint32)));
64 | pigler.removeAllNotifications();
65 | piglerId = 0;
66 | }
67 | #endif
68 | }
69 |
70 | #ifdef SYMBIAN3_READY
71 | void PlatformUtils::piglerHandleTap(qint32 notificationId)
72 | {
73 | //App should be opened automatically
74 | unread.clear();
75 | }
76 | #endif
77 |
78 | void PlatformUtils::showAndRaise()
79 | {
80 | //TODO remove notifications
81 | unread.clear();
82 |
83 | if (window) {
84 | window->show();
85 | window->activateWindow();
86 | window->raise();
87 | }
88 | }
89 |
90 | void PlatformUtils::quit()
91 | {
92 | QApplication::exit();
93 | }
94 |
95 | #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINPHONE)
96 | void PlatformUtils::trayActivated(QSystemTrayIcon::ActivationReason reason)
97 | {
98 | if (reason != QSystemTrayIcon::Context) {
99 | showAndRaise();
100 | }
101 | }
102 |
103 | void PlatformUtils::messageClicked()
104 | {
105 | showAndRaise();
106 | }
107 |
108 | void PlatformUtils::menuTriggered(QAction *action)
109 | {
110 | if (action->text() == "Exit") {
111 | quit();
112 | return;
113 | }
114 |
115 | showAndRaise();
116 | }
117 | #endif
118 |
119 | void PlatformUtils::windowsExtendFrameIntoClientArea(int left, int top, int right, int bottom)
120 | {
121 | #ifdef DWM_FEATURES
122 | if (window) {
123 | window->setAttribute(Qt::WA_TranslucentBackground, true);
124 | window->setAttribute(Qt::WA_NoSystemBackground, false);
125 | window->setStyleSheet("background: transparent");
126 | QtWin::extendFrameIntoClientArea(window, left, top, right, bottom);
127 | }
128 | #endif
129 | }
130 |
131 | bool PlatformUtils::windowsIsCompositionEnabled()
132 | {
133 | #ifdef DWM_FEATURES
134 | return QtWin::isCompositionEnabled();
135 | #else
136 | return false;
137 | #endif
138 | }
139 |
140 | QColor PlatformUtils::windowsRealColorizationColor()
141 | {
142 | #ifdef DWM_FEATURES
143 | return QtWin::realColorizationColor();
144 | #else
145 | return Qt::white;
146 | #endif
147 | }
148 |
149 | bool PlatformUtils::isWindows()
150 | {
151 | #ifdef DWM_FEATURES
152 | return true;
153 | #else
154 | return false;
155 | #endif
156 | }
157 |
158 | void PlatformUtils::gotNewMessage(qint64 peerId, QString peerName, QString senderName, QString text, bool silent)
159 | {
160 | if (window && window->hasFocus()) {
161 | unread.clear();
162 | return;
163 | }
164 |
165 | QVariantMap info;
166 | info["id"] = peerId;
167 | info["peerName"] = peerName;
168 | info["senderName"] = senderName;
169 | info["text"] = text;
170 |
171 | unread.insert(peerId, info);
172 |
173 | QString title;
174 | QString message;
175 |
176 | title = peerName;
177 | message = senderName;
178 | message += text;
179 |
180 | #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINPHONE)
181 | if (!silent) {
182 | kgDebug() << "Sending Windows notification";
183 | trayIcon.showMessage(title, message);
184 | }
185 | #endif
186 |
187 | if (unread.size() != 1) {
188 | title = "New messages from " + QString::number(unread.size()) + " chats";
189 | foreach (qint32 pid, unread.keys()) {
190 | if (!message.isEmpty()) {
191 | message += ", ";
192 | }
193 | message += unread[pid]["peerName"].toString();
194 | }
195 | }
196 |
197 | title = title.left(63);
198 | message = message.left(63);
199 |
200 | #ifdef SYMBIAN3_READY
201 | kgDebug() << "Sending Symbian notification";
202 | TUid symbianUid = {SYMBIAN_UID};
203 | //TODO: icon
204 | TRAP_IGNORE(CAknDiscreetPopup::ShowGlobalPopupL(TPtrC16(title.utf16()), TPtrC16(message.utf16()), KAknsIIDNone, KNullDesC, 0, 0, 1, 0, 0, symbianUid));
205 | #endif
206 |
207 | #ifdef SYMBIAN3_READY
208 | kgDebug() << "Sending Pigler notification";
209 | if (piglerId == 0) {
210 | piglerId = pigler.createNotification(title, message);
211 | } else if (piglerId > 0) {
212 | pigler.updateNotification(piglerId, title, message);
213 | } else {
214 | kgDebug() << "Pigler is not initialized";
215 | }
216 |
217 | if (piglerId > 0) {
218 | static QImage piglerImage(":/kutegramquick_pigler.png");
219 | pigler.setNotificationIcon(piglerId, piglerImage);
220 | }
221 | #endif
222 |
223 | //TODO: notify only when unfocused?
224 | //TODO: custom notification popup for Windows/legacy Symbian
225 | //TODO: android
226 | //TODO: vibrate
227 | //TODO: sound
228 | //TODO: blink
229 | }
230 |
231 | void openUrl(QUrl url)
232 | {
233 | #ifdef Q_OS_SYMBIAN
234 | static TUid KUidBrowser = {0x10008D39};
235 | _LIT(KBrowserPrefix, "4 ");
236 |
237 | // convert url to encoded version of QString
238 | QString encUrl(QString::fromUtf8(url.toEncoded()));
239 | // using qt_QString2TPtrC() based on
240 | //
241 | TPtrC tUrl(TPtrC16(static_cast(encUrl.utf16()), encUrl.length()));
242 |
243 | // Following code based on
244 | //
245 |
246 | // create a session with apparc server
247 | RApaLsSession appArcSession;
248 | User::LeaveIfError(appArcSession.Connect());
249 | CleanupClosePushL(appArcSession);
250 |
251 | // get the default application uid for application/x-web-browse
252 | TDataType mimeDatatype(_L8("application/x-web-browse"));
253 | TUid handlerUID;
254 | appArcSession.AppForDataType(mimeDatatype, handlerUID);
255 |
256 | // if UiD not found, use the native browser
257 | if (handlerUID.iUid == 0 || handlerUID.iUid == -1)
258 | handlerUID = KUidBrowser;
259 |
260 | // Following code based on
261 | //
262 |
263 | HBufC* buf16 = HBufC::NewLC(tUrl.Length() + KBrowserPrefix.iTypeLength);
264 | buf16->Des().Copy(KBrowserPrefix); // Prefix used to launch correct browser view
265 | buf16->Des().Append(tUrl);
266 |
267 | TApaTaskList taskList(CCoeEnv::Static()->WsSession());
268 | TApaTask task = taskList.FindApp(handlerUID);
269 | if (task.Exists()) {
270 | // Switch to existing browser instance
271 | task.BringToForeground();
272 | HBufC8* param8 = HBufC8::NewLC(buf16->Length());
273 | param8->Des().Append(buf16->Des());
274 | task.SendMessage(TUid::Uid(0), *param8); // Uid is not used
275 | CleanupStack::PopAndDestroy(param8);
276 | } else {
277 | // Start a new browser instance
278 | TThreadId id;
279 | appArcSession.StartDocument(*buf16, handlerUID, id);
280 | }
281 |
282 | CleanupStack::PopAndDestroy(buf16);
283 | CleanupStack::PopAndDestroy(&appArcSession);
284 | #else
285 | QDesktopServices::openUrl(url);
286 | #endif
287 | }
288 |
--------------------------------------------------------------------------------
/src/foldersmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "foldersmodel.h"
2 |
3 | #include "tlschema.h"
4 | #include
5 |
6 | FoldersModel::FoldersModel(QObject *parent)
7 | : QAbstractListModel(parent)
8 | , _mutex(QMutex::Recursive)
9 | , _folders()
10 | , _client(0)
11 | , _userId(0)
12 | , _requestId(0)
13 | {
14 | #if QT_VERSION < 0x050000
15 | setRoleNames(roleNames());
16 | #endif
17 | }
18 |
19 | void FoldersModel::resetState()
20 | {
21 | if (!_folders.isEmpty()) {
22 | beginRemoveRows(QModelIndex(), 0, _folders.size() - 1);
23 | _folders.clear();
24 | endRemoveRows();
25 | emit foldersChanged(_folders);
26 | }
27 |
28 | _requestId = 0;
29 | }
30 |
31 | QHash FoldersModel::roleNames() const
32 | {
33 | static QHash roles;
34 |
35 | if (!roles.isEmpty())
36 | return roles;
37 |
38 | roles[TitleRole] = "title";
39 | roles[IconRole] = "icon";
40 | roles[FolderIndexRole] = "folderIndex";
41 |
42 | return roles;
43 | }
44 |
45 | void FoldersModel::setClient(QObject *client)
46 | {
47 | QMutexLocker lock(&_mutex);
48 |
49 | if (_client) {
50 | _client->disconnect(this);
51 | }
52 |
53 | _client = dynamic_cast(client);
54 | _userId = 0;
55 |
56 | resetState();
57 |
58 | if (!_client) return;
59 |
60 | connect(_client, SIGNAL(authorized(TgLongVariant)), this, SLOT(authorized(TgLongVariant)));
61 | connect(_client, SIGNAL(vectorDialogFilterResponse(TgVector,TgLongVariant)), this, SLOT(messagesGetDialogFiltersResponse(TgVector,TgLongVariant)));
62 | }
63 |
64 | QObject* FoldersModel::client() const
65 | {
66 | return _client;
67 | }
68 |
69 | int FoldersModel::rowCount(const QModelIndex &parent) const
70 | {
71 | return _folders.size();
72 | }
73 |
74 | QVariant FoldersModel::data(const QModelIndex &index, int role) const
75 | {
76 | if (role == FolderIndexRole) {
77 | return index.row();
78 | }
79 |
80 | return _folders[index.row()][roleNames()[role]];
81 | }
82 |
83 | bool FoldersModel::canFetchMoreDownwards() const
84 | {
85 | return _client && _client->isAuthorized() && !_requestId.toLongLong() && _folders.isEmpty();
86 | }
87 |
88 | void FoldersModel::fetchMoreDownwards()
89 | {
90 | QMutexLocker lock(&_mutex);
91 |
92 | _requestId = _client->messagesGetDialogFilters();
93 | }
94 |
95 | void FoldersModel::authorized(TgLongVariant userId)
96 | {
97 | QMutexLocker lock(&_mutex);
98 |
99 | if (_userId != userId) {
100 | resetState();
101 | _userId = userId;
102 | fetchMoreDownwards();
103 | }
104 | }
105 |
106 | void FoldersModel::messagesGetDialogFiltersResponse(TgVector data, TgLongVariant messageId)
107 | {
108 | QMutexLocker lock(&_mutex);
109 |
110 | if (_requestId != messageId) {
111 | return;
112 | }
113 |
114 | _requestId = 0;
115 |
116 | if (data.isEmpty()) {
117 | return;
118 | }
119 |
120 | QList rows;
121 | rows.reserve(data.size());
122 |
123 | for (qint32 i = 0; i < data.size(); ++i) {
124 | rows.append(createRow(data[i].toMap()));
125 | }
126 |
127 | beginInsertRows(QModelIndex(), _folders.size(), _folders.size() + rows.size() - 1);
128 | _folders.append(rows);
129 | endInsertRows();
130 | emit foldersChanged(_folders);
131 | }
132 |
133 | #define ICON(key, image) map[key] = image;
134 |
135 | QHash getIconsMap()
136 | {
137 | //Referenced from Telegram Desktop:
138 | //https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/ui/filter_icons.cpp#L17
139 | QHash map;
140 |
141 | ICON("\xF0\x9F\x90\xB1", "cat.png");
142 | ICON("\xF0\x9F\x93\x95", "book.png");
143 | ICON("\xF0\x9F\x92\xB0", "money.png");
144 | ICON("\xF0\x9F\x8E\xAE", "game.png");
145 | ICON("\xF0\x9F\x92\xA1", "light.png");
146 | ICON("\xF0\x9F\x91\x8C", "like.png");
147 | ICON("\xF0\x9F\x8E\xB5", "note.png");
148 | ICON("\xF0\x9F\x8E\xA8", "palette.png");
149 | ICON("\xE2\x9C\x88\xEF\xB8\x8F", "travel.png");
150 | ICON("\xE2\x9A\xBD\xEF\xB8\x8F", "sport.png");
151 | ICON("\xE2\xAD\x90", "favorite.png");
152 | ICON("\xF0\x9F\x8E\x93", "study.png");
153 | ICON("\xF0\x9F\x9B\xAB", "airplane.png");
154 | ICON("\xF0\x9F\x91\xA4", "private.png");
155 | ICON("\xF0\x9F\x91\xA5", "groups.png");
156 | ICON("\xF0\x9F\x92\xAC", "all.png");
157 | ICON("\xE2\x9C\x85", "unread.png");
158 | ICON("\xF0\x9F\xA4\x96", "bots.png");
159 | ICON("\xF0\x9F\x91\x91", "crown.png");
160 | ICON("\xF0\x9F\x8C\xB9", "flower.png");
161 | ICON("\xF0\x9F\x8F\xA0", "home.png");
162 | ICON("\xE2\x9D\xA4", "love.png");
163 | ICON("\xF0\x9F\x8E\xAD", "mask.png");
164 | ICON("\xF0\x9F\x8D\xB8", "party.png");
165 | ICON("\xF0\x9F\x93\x88", "trade.png");
166 | ICON("\xF0\x9F\x92\xBC", "work.png");
167 | ICON("\xF0\x9F\x94\x94", "unmuted.png");
168 | ICON("\xF0\x9F\x93\xA2", "channels.png");
169 | ICON("\xF0\x9F\x93\x81", "custom.png");
170 | ICON("\xF0\x9F\x93\x8B", "setup.png");
171 |
172 | return map;
173 | }
174 |
175 | TgObject FoldersModel::createRow(TgObject filter)
176 | {
177 | if (GETID(filter) == TLType::DialogFilterDefault) {
178 | filter["title"] = "All chats";
179 | filter["icon"] = "../../img/filters/all.png";
180 |
181 | return filter;
182 | }
183 |
184 | static QHash iconsMap = getIconsMap();
185 | QString icon = iconsMap[filter["emoticon"].toString()];
186 | if (!icon.isEmpty()) {
187 | filter["icon"] = QString("../../img/filters/" + icon);
188 |
189 | return filter;
190 | }
191 |
192 | //Referenced from Telegram Desktop:
193 | //https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/ui/filter_icons.cpp#L242
194 |
195 | quint32 flags = filter["flags"].toUInt();
196 |
197 | const quint32 allFlags = 31;
198 |
199 | icon = "custom.png";
200 |
201 | if (!filter["include_peers"].toList().isEmpty()
202 | || !filter["exclude_peers"].toList().isEmpty()
203 | || !(flags & allFlags)) {
204 | icon = "custom.png";
205 | } else if ((flags & allFlags) == 1
206 | || (flags & allFlags) == 2
207 | || (flags & allFlags) == 3) {
208 | icon = "private.png";
209 | } else if ((flags & allFlags) == 4) {
210 | icon = "groups.png";
211 | } else if ((flags & allFlags) == 8) {
212 | icon = "channels.png";
213 | } else if ((flags & allFlags) == 16) {
214 | icon = "bots.png";
215 | } else if ((flags & 2048) == 2048) {
216 | icon = "unread.png";
217 | } else if ((flags & 4096) == 4096) {
218 | icon = "unmuted.png";
219 | }
220 |
221 | filter["icon"] = QString("../../img/filters/" + icon);
222 |
223 | return filter;
224 | }
225 |
226 | bool FoldersModel::matchesFilter(TgObject filter, TgObject peer)
227 | {
228 | if (GETID(filter) == TLType::DialogFilterDefault) {
229 | return true;
230 | }
231 |
232 | TgList includePeers = filter["include_peers"].toList();
233 | for (qint32 i = 0; i < includePeers.size(); ++i) {
234 | if (TgClient::peersEqual(peer, includePeers[i].toMap())) {
235 | return true;
236 | }
237 | }
238 |
239 | if (GETID(filter) == TLType::DialogFilterChatlist) {
240 | return false;
241 | }
242 |
243 | TgList excludePeers = filter["exclude_peers"].toList();
244 | for (qint32 i = 0; i < excludePeers.size(); ++i) {
245 | if (TgClient::peersEqual(peer, excludePeers[i].toMap())) {
246 | return false;
247 | }
248 | }
249 |
250 | if (filter["exclude_muted"].toBool() && peer["notify_settings"].toMap()["silent"].toBool()) {
251 | return false;
252 | }
253 |
254 | if (filter["exclude_read"].toBool() && !peer["unread_mark"].toBool()
255 | && !peer["unread_count"].toBool()
256 | && !peer["unread_mentions_count"].toBool()
257 | && !peer["unread_reactions_count"].toBool()) {
258 | return false;
259 | }
260 |
261 | if (filter["exclude_archived"].toBool() && peer["folder_id"].toBool()) {
262 | return false;
263 | }
264 |
265 | if (filter["contacts"].toBool() && TgClient::isUser(peer) && peer["contact"].toBool()) {
266 | return true;
267 | }
268 |
269 | if (filter["non_contacts"].toBool() && TgClient::isUser(peer) && !peer["contact"].toBool()) {
270 | return true;
271 | }
272 |
273 | if (filter["groups"].toBool() && TgClient::isGroup(peer)) {
274 | return true;
275 | }
276 |
277 | if (filter["broadcasts"].toBool() && TgClient::isChannel(peer)) {
278 | return true;
279 | }
280 |
281 | if (filter["bots"].toBool() && TgClient::isUser(peer) && peer["bot"].toBool()) {
282 | return true;
283 | }
284 |
285 | return false;
286 | }
287 |
288 | void FoldersModel::refresh()
289 | {
290 | resetState();
291 | fetchMoreDownwards();
292 | }
293 |
294 | QList FoldersModel::folders()
295 | {
296 | return _folders;
297 | }
298 |
--------------------------------------------------------------------------------
/qml/control/ImageViewer.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 |
3 | Item {
4 | id: imageViewer
5 |
6 | property int scaling: Math.min(100, Math.min(Math.floor(imageViewer.width / innerImage.sourceSize.width * 100), Math.floor(imageViewer.height / innerImage.sourceSize.height * 100)))
7 | property alias imageSource: innerImage.source
8 |
9 | //TODO loading spinner
10 |
11 | state: "CLOSED"
12 | visible: opacity != 0
13 | states: [
14 | State {
15 | name: "OPENED"
16 | PropertyChanges {
17 | target: imageViewer
18 | opacity: 1
19 | }
20 | },
21 | State {
22 | name: "CLOSED"
23 | PropertyChanges {
24 | target: imageViewer
25 | opacity: 0
26 | }
27 | }
28 | ]
29 |
30 | Behavior on opacity {
31 | NumberAnimation {
32 | easing.type: Easing.InOutQuad
33 | }
34 | }
35 |
36 | onWidthChanged: {
37 | changeTimer.restart();
38 | }
39 |
40 | onVisibleChanged: {
41 | if (!visible)
42 | imageSource = "";
43 | }
44 |
45 | Timer {
46 | id: changeTimer
47 | interval: 200
48 | repeat: false
49 | onTriggered: {
50 | scaling = Math.min(100, Math.min(Math.floor(imageViewer.width / innerImage.sourceSize.width * 100), Math.floor(imageViewer.height / innerImage.sourceSize.height * 100)));
51 | flickable.contentX = 0;
52 | flickable.contentY = 0;
53 | }
54 | }
55 |
56 | Rectangle {
57 | anchors.fill: parent
58 | color: "black"
59 | opacity: 0.8
60 | }
61 |
62 | MouseArea {
63 | anchors.fill: parent
64 | onClicked: {
65 | imageViewer.state = "CLOSED"
66 | }
67 | }
68 |
69 | Flickable {
70 | id: flickable
71 | anchors.centerIn: parent
72 |
73 | width: Math.min(parent.width, innerImage.width)
74 | height: Math.min(parent.height, innerImage.height)
75 |
76 | contentWidth: innerImage.width
77 | contentHeight: innerImage.height
78 |
79 | //TODO think about image center drift on first zoom
80 | //TODO protect contentX and contentY from spam zooming
81 |
82 | Behavior on contentX {
83 | enabled: !flickable.flicking
84 | NumberAnimation {
85 | easing.type: Easing.InOutQuad
86 | }
87 | }
88 |
89 | Behavior on contentY {
90 | enabled: !flickable.flicking
91 | NumberAnimation {
92 | easing.type: Easing.InOutQuad
93 | }
94 | }
95 |
96 | Image {
97 | id: innerImage
98 | smooth: true
99 |
100 | onStatusChanged: {
101 | if (innerImage.status == Image.Ready) {
102 | scaling = Math.min(100, Math.min(Math.floor(imageViewer.width / innerImage.sourceSize.width * 100), Math.floor(imageViewer.height / innerImage.sourceSize.height * 100)))
103 | }
104 | }
105 |
106 | width: sourceSize.width * scaling / 100
107 | height: sourceSize.height * scaling / 100
108 |
109 | Behavior on width {
110 | NumberAnimation {
111 | easing.type: Easing.InOutQuad
112 | }
113 | }
114 |
115 | Behavior on height {
116 | NumberAnimation {
117 | easing.type: Easing.InOutQuad
118 | }
119 | }
120 | }
121 | }
122 |
123 | Rectangle {
124 | id: topLeftRect
125 | anchors.left: parent.left
126 | anchors.top: parent.top
127 | anchors.leftMargin: 10 * kgScaling
128 | anchors.topMargin: 10 * kgScaling
129 | height: 40 * kgScaling
130 | width: 40 * kgScaling
131 | color: "black"
132 | opacity: 0.5
133 | radius: 10 * kgScaling
134 | smooth: true
135 |
136 | MouseArea {
137 | anchors.fill: parent
138 | onClicked: {
139 | imageViewer.state = "CLOSED"
140 | }
141 | }
142 | }
143 |
144 | Image {
145 | anchors.centerIn: topLeftRect
146 | width: 20 * kgScaling
147 | height: width
148 | smooth: true
149 | source: "../../img/arrow-left.png"
150 | asynchronous: true
151 | }
152 |
153 | Rectangle {
154 | id: topRightRect
155 | anchors.right: parent.right
156 | anchors.top: parent.top
157 | anchors.rightMargin: 10 * kgScaling
158 | anchors.topMargin: 10 * kgScaling
159 | height: 40 * kgScaling
160 | width: 40 * kgScaling
161 | color: "black"
162 | opacity: 0.5
163 | radius: 10 * kgScaling
164 | smooth: true
165 |
166 | MouseArea {
167 | anchors.fill: parent
168 | onClicked: {
169 | scaling = Math.min(100, Math.min(Math.floor(imageViewer.width / innerImage.sourceSize.width * 100), Math.floor(imageViewer.height / innerImage.sourceSize.height * 100)));
170 | flickable.contentX = 0;
171 | flickable.contentY = 0;
172 | }
173 | }
174 | }
175 |
176 | Image {
177 | anchors.centerIn: topRightRect
178 | width: 20 * kgScaling
179 | height: width
180 | smooth: true
181 | source: "../../img/fullscreen.png"
182 | asynchronous: true
183 | }
184 |
185 | Rectangle {
186 | id: bottomLeftRect
187 | anchors.left: parent.left
188 | anchors.bottom: parent.bottom
189 | anchors.leftMargin: 10 * kgScaling
190 | anchors.bottomMargin: 10 * kgScaling
191 | height: 40 * kgScaling
192 | width: 40 * kgScaling
193 | color: "black"
194 | opacity: 0.5
195 | radius: 10 * kgScaling
196 | smooth: true
197 |
198 | MouseArea {
199 | anchors.fill: parent
200 | onClicked: {
201 | var newScaling = Math.ceil(scaling / 25) * 25;
202 |
203 | if (scaling > 300)
204 | newScaling -= 100;
205 | else if (scaling > 100)
206 | newScaling -= 50;
207 | else if (scaling > 25)
208 | newScaling -= 25;
209 |
210 | flickable.contentX = (flickable.contentX + imageViewer.width / 2) * newScaling / scaling
211 | - imageViewer.width / 2;
212 | flickable.contentY = (flickable.contentY + imageViewer.height / 2) * newScaling / scaling
213 | - imageViewer.height / 2;
214 |
215 |
216 | scaling = newScaling;
217 | }
218 | }
219 | }
220 |
221 | Image {
222 | anchors.centerIn: bottomLeftRect
223 | width: 20 * kgScaling
224 | height: width
225 | smooth: true
226 | source: "../../img/magnify-minus-outline.png"
227 | asynchronous: true
228 | }
229 |
230 | Rectangle {
231 | id: bottomRightRect
232 | anchors.right: parent.right
233 | anchors.bottom: parent.bottom
234 | anchors.rightMargin: 10 * kgScaling
235 | anchors.bottomMargin: 10 * kgScaling
236 | height: 40 * kgScaling
237 | width: 40 * kgScaling
238 | color: "black"
239 | opacity: 0.5
240 | radius: 10 * kgScaling
241 | smooth: true
242 |
243 | MouseArea {
244 | anchors.fill: parent
245 | onClicked: {
246 | var newScaling = Math.ceil(scaling / 25) * 25;
247 |
248 | if (scaling < 100)
249 | newScaling += 25;
250 | else if (scaling < 300)
251 | newScaling += 50;
252 | else if (scaling < 800)
253 | newScaling += 100;
254 |
255 | flickable.contentX = (flickable.contentX + imageViewer.width / 2) * newScaling / scaling
256 | - imageViewer.width / 2;
257 | flickable.contentY = (flickable.contentY + imageViewer.height / 2) * newScaling / scaling
258 | - imageViewer.height / 2;
259 |
260 | scaling = newScaling;
261 | }
262 | }
263 | }
264 |
265 | Image {
266 | anchors.centerIn: bottomRightRect
267 | width: 20 * kgScaling
268 | height: width
269 | smooth: true
270 | source: "../../img/magnify-plus-outline.png"
271 | asynchronous: true
272 | }
273 |
274 | Rectangle {
275 | id: bottomCenterRect
276 | anchors.horizontalCenter: parent.horizontalCenter
277 | anchors.bottom: parent.bottom
278 | anchors.bottomMargin: (60 * kgScaling - height) / 2
279 | height: 16 * kgScaling + innerText.height
280 | width: 16 * kgScaling + innerText.width
281 | color: "black"
282 | opacity: 0.5
283 | radius: 8 * kgScaling
284 | smooth: true
285 | }
286 |
287 | Text {
288 | property int scalingAnimated: scaling
289 |
290 | Behavior on scalingAnimated {
291 | NumberAnimation {
292 | easing.type: Easing.InOutQuad
293 | }
294 | }
295 |
296 | id: innerText
297 | anchors.centerIn: bottomCenterRect
298 | text: scalingAnimated + "%"
299 | color: "white"
300 | font.pixelSize: 12 * kgScaling
301 | }
302 | }
303 |
304 |
--------------------------------------------------------------------------------
/qml/message/MessageEdit.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import Kutegram 1.0
3 | import "../control"
4 |
5 | Rectangle {
6 | height: 40 * kgScaling
7 | width: 240 * kgScaling
8 | color: "#FFFFFF"
9 | id: editRoot
10 |
11 | property alias messageText: innerEdit.text
12 |
13 | function uploadingProgress(progress) {
14 | if (progress != -1 && progress != 100) {
15 | attachButton.state = "UPLOADING";
16 | uploadBar.width = progress * editRoot.width / 100;
17 | } else {
18 | attachButton.state = progress != 100 ? "NOT_UPLOADING" : "UPLOADED";
19 | uploadBar.width = 0;
20 | }
21 | }
22 |
23 | MouseArea {
24 | anchors.fill: parent
25 | }
26 |
27 | Item {
28 | id: attachButton
29 | anchors.top: parent.top
30 | anchors.bottom: parent.bottom
31 | anchors.left: parent.left
32 | width: height
33 | state: "NOT_UPLOADING"
34 |
35 | MouseArea {
36 | anchors.fill: parent
37 | onClicked: {
38 | if (attachButton.state == "NOT_UPLOADING") {
39 | messagesModel.uploadFile();
40 | } else {
41 | messagesModel.cancelUpload();
42 | }
43 | }
44 | }
45 |
46 | Image {
47 | id: attachmentImage
48 | anchors.centerIn: parent
49 | width: 20 * kgScaling
50 | height: width
51 | smooth: true
52 | source: "../../img/attachment.png"
53 | rotation: 135
54 | asynchronous: true
55 | }
56 |
57 | Image {
58 | id: deleteImage
59 | anchors.centerIn: parent
60 | width: 20 * kgScaling
61 | height: width
62 | smooth: true
63 | source: "../../img/delete.png"
64 | asynchronous: true
65 | }
66 |
67 | Spinner {
68 | id: uploadSpinner
69 | anchors.centerIn: parent
70 |
71 | Image {
72 | anchors.centerIn: parent
73 | width: 20 * kgScaling
74 | height: width
75 | smooth: true
76 | source: "../../img/close-circle-outline_inner.png"
77 | asynchronous: true
78 | }
79 | }
80 |
81 | states: [
82 | State {
83 | name: "UPLOADING"
84 | PropertyChanges {
85 | target: attachmentImage
86 | opacity: 0
87 | scale: 0
88 | }
89 | PropertyChanges {
90 | target: deleteImage
91 | opacity: 0
92 | scale: 0
93 | }
94 | PropertyChanges {
95 | target: uploadSpinner
96 | opacity: 1
97 | scale: 1
98 | }
99 | },
100 | State {
101 | name: "NOT_UPLOADING"
102 | PropertyChanges {
103 | target: attachmentImage
104 | opacity: 1
105 | scale: 1
106 | }
107 | PropertyChanges {
108 | target: deleteImage
109 | opacity: 0
110 | scale: 0
111 | }
112 | PropertyChanges {
113 | target: uploadSpinner
114 | opacity: 0
115 | scale: 0
116 | }
117 | },
118 | State {
119 | name: "UPLOADED"
120 | PropertyChanges {
121 | target: attachmentImage
122 | opacity: 0
123 | scale: 0
124 | }
125 | PropertyChanges {
126 | target: deleteImage
127 | opacity: 1
128 | scale: 1
129 | }
130 | PropertyChanges {
131 | target: uploadSpinner
132 | opacity: 0
133 | scale: 0
134 | }
135 | }
136 | ]
137 |
138 | transitions: [
139 | Transition {
140 | NumberAnimation {
141 | properties: "opacity,scale"
142 | easing.type: Easing.InOutQuad
143 | duration: 200
144 | }
145 | }
146 | ]
147 | }
148 |
149 | Item {
150 | id: sendButton
151 | anchors.top: parent.top
152 | anchors.bottom: parent.bottom
153 | anchors.right: parent.right
154 | width: height
155 | state: messageText.length == 0 && attachButton.state != "UPLOADED" ? "EMPTY" : "NOT_EMPTY"
156 |
157 | MouseArea {
158 | anchors.fill: parent
159 | onClicked: {
160 | if (sendButton.state == "NOT_EMPTY") {
161 | messagesModel.sendMessage(messageText);
162 | messageText = "";
163 | }
164 | }
165 | }
166 |
167 | Image {
168 | id: sendImage
169 | anchors.centerIn: parent
170 | width: 20 * kgScaling
171 | height: width
172 | smooth: true
173 | source: "../../img/send_accent.png"
174 | asynchronous: true
175 | }
176 |
177 | Image {
178 | id: micImage
179 | anchors.centerIn: parent
180 | width: 20 * kgScaling
181 | height: width
182 | smooth: true
183 | //source: "../../img/microphone.png"
184 | source: "../../img/send.png"
185 | asynchronous: true
186 | }
187 |
188 | states: [
189 | State {
190 | name: "EMPTY"
191 | PropertyChanges {
192 | target: sendImage
193 | opacity: 0
194 | scale: 0
195 | }
196 | PropertyChanges {
197 | target: micImage
198 | opacity: 1
199 | scale: 1
200 | }
201 | },
202 | State {
203 | name: "NOT_EMPTY"
204 | PropertyChanges {
205 | target: sendImage
206 | opacity: 1
207 | scale: 1
208 | }
209 | PropertyChanges {
210 | target: micImage
211 | opacity: 0
212 | scale: 0
213 | }
214 | }
215 | ]
216 |
217 | transitions: [
218 | Transition {
219 | NumberAnimation {
220 | properties: "opacity,scale"
221 | easing.type: Easing.InOutQuad
222 | duration: 200
223 | }
224 | }
225 | ]
226 | }
227 |
228 | Flickable {
229 | anchors.top: parent.top
230 | anchors.bottom: parent.bottom
231 | anchors.left: attachButton.right
232 | anchors.right: sendButton.left
233 | anchors.margins: 2 * kgScaling
234 | clip: true
235 | id: innerFlick
236 |
237 | function ensureVisible(r)
238 | {
239 | if (contentX >= r.x)
240 | contentX = r.x;
241 | else if (contentX+width <= r.x+r.width)
242 | contentX = r.x+r.width-width;
243 | if (contentY >= r.y)
244 | contentY = r.y;
245 | else if (contentY+height <= r.y+r.height)
246 | contentY = r.y+r.height-height;
247 | }
248 |
249 | Item {
250 | id: editContainer
251 | anchors.top: parent.top
252 | anchors.left: parent.left
253 | anchors.right: parent.right
254 | height: parent.height
255 | anchors.topMargin: Math.max((parent.height - innerEdit.paintedHeight) / 2, 0)
256 |
257 | TextEdit {
258 | id: innerEdit
259 | anchors.fill: parent
260 |
261 | wrapMode: TextEdit.Wrap
262 | font.pixelSize: 12 * kgScaling
263 | onCursorRectangleChanged: innerFlick.ensureVisible(cursorRectangle)
264 | }
265 | }
266 |
267 | Text {
268 | id: messageTip
269 | anchors.top: editContainer.top
270 | anchors.left: parent.left
271 | anchors.right: parent.right
272 | color: "#8D8D8D"
273 | text: "Write a message..."
274 | font.pixelSize: 12 * kgScaling
275 | state: messageText.length == 0 ? "EMPTY" : "NOT_EMPTY"
276 |
277 | states: [
278 | State {
279 | name: "EMPTY"
280 | PropertyChanges {
281 | target: messageTip
282 | opacity: 1
283 | anchors.leftMargin: 0
284 | }
285 | },
286 | State {
287 | name: "NOT_EMPTY"
288 | PropertyChanges {
289 | target: messageTip
290 | opacity: 0
291 | anchors.leftMargin: -10 * kgScaling
292 | }
293 | }
294 | ]
295 |
296 | transitions: [
297 | Transition {
298 | NumberAnimation {
299 | properties: "opacity,anchors.leftMargin"
300 | easing.type: Easing.InOutQuad
301 | duration: 200
302 | }
303 | }
304 | ]
305 | }
306 | }
307 |
308 | Rectangle {
309 | id: uploadBar
310 | anchors.bottom: parent.bottom
311 | anchors.left: parent.left
312 | height: 2 * kgScaling
313 | color: globalAccent
314 | width: 0
315 |
316 | Behavior on width {
317 | NumberAnimation {
318 | easing.type: Easing.InOutQuad
319 | }
320 | }
321 | }
322 |
323 | Rectangle {
324 | height: 1 * kgScaling
325 | anchors.left: parent.left
326 | anchors.right: parent.right
327 | anchors.top: parent.top
328 | color: "#EEEEEE"
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/qml/control/Drawer.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import Kutegram 1.0
3 |
4 | Item {
5 | property bool opened: false
6 |
7 | CurrentUserInfo {
8 | id: currentUserInfo
9 | client: telegramClient
10 | avatarDownloader: globalAvatarDownloader
11 |
12 | onUserAvatarDownloaded: {
13 | peerAvatar = avatar;
14 | }
15 |
16 | onUserInfoChanged: {
17 | peerThumbnailText = thumbnailText;
18 | peerThumbnailColor = thumbnailColor;
19 | peerName = name;
20 | peerUsername = username;
21 | }
22 | }
23 |
24 | property string peerAvatar: ""
25 | property string peerThumbnailText: ""
26 | property color peerThumbnailColor: "#00000000"
27 | property string peerName: ""
28 | property string peerUsername: ""
29 |
30 | id: drawerRoot
31 | width: 240
32 | height: 320
33 |
34 | state: opened ? "OPENED" : "CLOSED"
35 |
36 | states: [
37 | State {
38 | name: "OPENED"
39 | PropertyChanges {
40 | target: dimmBackground
41 | opacity: 0.5
42 | }
43 | PropertyChanges {
44 | target: drawerSlide
45 | currentIndex: 0
46 | anchors.leftMargin: 0
47 | }
48 | },
49 | State {
50 | name: "CLOSED"
51 | PropertyChanges {
52 | target: dimmBackground
53 | opacity: 0
54 | }
55 | PropertyChanges {
56 | target: drawerSlide
57 | anchors.leftMargin: -Math.max(240, drawerRoot.width)
58 | }
59 | }
60 | ]
61 |
62 | transitions: [
63 | Transition {
64 | NumberAnimation {
65 | properties: "opacity,anchors.leftMargin"
66 | easing.type: Easing.InOutQuad
67 | duration: 200
68 | }
69 | }
70 | ]
71 |
72 | Rectangle {
73 | id: dimmBackground
74 | anchors.fill: parent
75 | color: "#000000"
76 | }
77 |
78 | function closeDrawer() {
79 | topBar.currentState = "MENU"
80 | opened = false;
81 | }
82 |
83 | ListView {
84 | id: drawerSlide
85 | anchors.left: parent.left
86 | width: drawerRoot.width
87 | anchors.top: parent.top
88 | anchors.bottom: parent.bottom
89 | boundsBehavior: Flickable.StopAtBounds
90 | orientation: ListView.Horizontal
91 | snapMode: ListView.SnapOneItem
92 | highlightRangeMode: ListView.StrictlyEnforceRange
93 | highlightFollowsCurrentItem: true
94 | highlightMoveDuration: 200
95 |
96 | onCurrentItemChanged: {
97 | if (currentIndex == 1) {
98 | closeDrawer();
99 | }
100 | }
101 |
102 | model: VisualItemModel {
103 | Item {
104 | id: drawerContent
105 | width: drawerRoot.width
106 | height: drawerRoot.height
107 |
108 | MouseArea {
109 | id: dimmMouseArea
110 | anchors.fill: parent
111 |
112 | onClicked: {
113 | closeDrawer();
114 | }
115 | }
116 |
117 | Rectangle {
118 | anchors.top: parent.top
119 | anchors.left: parent.left
120 | anchors.bottom: parent.bottom
121 | width: Math.min(Math.max(240, parent.width * 5 / 6), 280 * kgScaling)
122 | color: "#FFFFFF"
123 |
124 | Rectangle {
125 | id: currentUserRect
126 | anchors.top: parent.top
127 | anchors.left: parent.left
128 | anchors.right: parent.right
129 | height: 90 * kgScaling
130 | color: globalAccent
131 |
132 | MouseArea {
133 | anchors.fill: parent
134 | }
135 |
136 | Rectangle {
137 | id: avatarRect
138 | visible: peerAvatar.length == 0 || avatarImage.status != Image.Ready
139 |
140 | anchors.left: parent.left
141 | anchors.top: parent.top
142 | anchors.topMargin: 8 * kgScaling
143 | anchors.leftMargin: anchors.topMargin
144 |
145 | width: 40 * kgScaling
146 | height: width
147 | smooth: true
148 |
149 | color: peerThumbnailColor
150 | radius: width / 2
151 |
152 | Text {
153 | anchors.fill: parent
154 | text: peerThumbnailText
155 | color: "#FFFFFF"
156 | font.bold: true
157 | font.pixelSize: 12 * kgScaling
158 | horizontalAlignment: Text.AlignHCenter
159 | verticalAlignment: Text.AlignVCenter
160 | }
161 | }
162 |
163 | Image {
164 | id: avatarImage
165 | visible: peerAvatar.length != 0
166 |
167 | anchors.left: parent.left
168 | anchors.top: parent.top
169 | anchors.topMargin: avatarRect.anchors.topMargin
170 | anchors.leftMargin: avatarRect.anchors.topMargin
171 |
172 | width: 40 * kgScaling
173 | height: width
174 | smooth: true
175 |
176 | asynchronous: true
177 | source: peerAvatar
178 | }
179 |
180 | Text {
181 | id: avatarName
182 | text: peerName
183 | color: "#FFFFFF"
184 | font.bold: true
185 | font.pixelSize: 12 * kgScaling
186 |
187 | anchors.left: parent.left
188 | anchors.top: avatarImage.bottom
189 | anchors.topMargin: avatarRect.anchors.topMargin
190 | anchors.leftMargin: avatarRect.anchors.topMargin
191 | anchors.right: parent.right
192 | anchors.rightMargin: avatarRect.anchors.topMargin
193 | }
194 |
195 | Text {
196 | id: avatarUsername
197 | text: peerUsername.length != 0 ? "@" + peerUsername : "no username"
198 | color: "#FFFFFF"
199 | font.pixelSize: 12 * kgScaling
200 |
201 | anchors.left: parent.left
202 | anchors.top: avatarName.bottom
203 | anchors.topMargin: 0
204 | anchors.leftMargin: avatarRect.anchors.topMargin
205 | anchors.right: parent.right
206 | anchors.rightMargin: avatarRect.anchors.topMargin
207 | }
208 | }
209 |
210 | ListView {
211 | id: drawerListView
212 | anchors.top: currentUserRect.bottom
213 | anchors.bottom: drawerBottom.top
214 | anchors.left: parent.left
215 | anchors.right: parent.right
216 | boundsBehavior: Flickable.StopAtBounds
217 |
218 | // highlight: Rectangle {
219 | // width: drawerListView.width
220 | // height: 40 * kgScaling
221 | // opacity: 0.1
222 | // color: "#000000"
223 | // }
224 |
225 | model: ListModel {
226 | ListElement {
227 | icon: "../../img/exit-to-app.png"
228 | name: "Log out"
229 | }
230 | // ListElement {
231 | // icon: "../../img/close.png"
232 | // name: "Close"
233 | // }
234 | }
235 |
236 | delegate: DrawerButton {
237 |
238 | }
239 | }
240 |
241 | Rectangle {
242 | id: drawerBottom
243 | anchors.bottom: parent.bottom
244 | anchors.left: parent.left
245 | anchors.right: parent.right
246 | color: "#FFFFFF"
247 | height: versionRow.height + 16 * kgScaling
248 |
249 | MouseArea {
250 | anchors.fill: parent
251 | }
252 |
253 | Text {
254 | id: versionRow
255 | anchors.bottom: parent.bottom
256 | anchors.left: parent.left
257 | anchors.leftMargin: 12 * kgScaling
258 | anchors.right: parent.right
259 | anchors.rightMargin: 12 * kgScaling
260 | anchors.bottomMargin: 8 * kgScaling
261 |
262 | color: "#999999"
263 | text: "Version " + kutegramVersion + " for " + kutegramPlatform
264 | elide: Text.ElideRight
265 | font.bold: true
266 | font.pixelSize: 12 * kgScaling
267 | }
268 | }
269 | }
270 | }
271 | Item {
272 | id: drawerSpace
273 | width: drawerRoot.width
274 | height: drawerRoot.height
275 | }
276 | }
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/qml/control/TopBar.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 1.0
2 | import "../dialog"
3 |
4 | Item {
5 | property string peerTitle: ""
6 | property color peerThumbnailColor: "#00000000"
7 | property string peerThumbnailText: ""
8 | property string peerAvatar: ""
9 | property string peerTooltip: ""
10 |
11 | id: topBarRoot
12 | height: 40 * kgScaling
13 | width: 240 * kgScaling
14 | anchors.top: parent.top
15 | anchors.left: parent.left
16 | anchors.right: parent.right
17 | state: currentState == "CHAT" ? "BACK" : currentState
18 |
19 | Component.onCompleted: {
20 | platformUtils.windowsExtendFrameIntoClientArea(0, height, 0, 0);
21 | }
22 |
23 | Rectangle {
24 | anchors.fill: parent
25 | color: globalAccent
26 | //opacity: platformUtils.windowsIsCompositionEnabled() ? 0 : 1
27 | }
28 |
29 | Rectangle {
30 | height: 1 * kgScaling
31 | anchors.left: parent.left
32 | anchors.right: parent.right
33 | anchors.bottom: parent.bottom
34 | color: Qt.darker(globalAccent)
35 | }
36 |
37 | Item {
38 | anchors.fill: parent
39 | anchors.leftMargin: parent.height
40 | state: currentState
41 |
42 | ListView {
43 | id: folderList
44 | clip: true
45 | boundsBehavior: Flickable.StopAtBounds
46 | anchors.left: parent.left
47 | width: parent.width
48 | height: parent.height
49 | orientation: ListView.Horizontal
50 | highlightFollowsCurrentItem: true
51 | highlightMoveDuration: 200
52 | highlightResizeDuration: 200
53 |
54 | currentIndex: currentFolderIndex
55 | onCurrentItemChanged: {
56 | currentFolderIndex = currentIndex
57 | }
58 |
59 | model: foldersModel
60 |
61 | delegate: FolderItem {
62 |
63 | }
64 |
65 | highlight: Item {
66 | Rectangle {
67 | anchors.left: parent.left
68 | anchors.right: parent.right
69 | anchors.bottom: parent.bottom
70 | anchors.bottomMargin: 1 * kgScaling
71 | height: 2 * kgScaling
72 | color: "#FFFFFF"
73 | }
74 |
75 | Behavior on x {
76 | NumberAnimation {
77 | easing.type: Easing.InOutQuad
78 | }
79 | }
80 | Behavior on width {
81 | NumberAnimation {
82 | easing.type: Easing.InOutQuad
83 | }
84 | }
85 | }
86 | }
87 |
88 | Item {
89 | id: peerHeader
90 | anchors.left: parent.left
91 | width: parent.width
92 | height: parent.height
93 |
94 | MouseArea {
95 | anchors.fill: parent
96 | }
97 |
98 | Rectangle {
99 | id: avatarRect
100 | visible: peerAvatar.length == 0 || avatarImage.status != Image.Ready
101 |
102 | anchors.left: parent.left
103 | anchors.verticalCenter: parent.verticalCenter
104 | anchors.leftMargin: (parent.height - width) / 2
105 |
106 | width: 30 * kgScaling
107 | height: width
108 | smooth: true
109 |
110 | color: peerThumbnailColor
111 | radius: width / 2
112 |
113 | Text {
114 | anchors.fill: parent
115 | text: peerThumbnailText
116 | color: "#FFFFFF"
117 | font.bold: true
118 | font.pixelSize: 12 * kgScaling
119 | horizontalAlignment: Text.AlignHCenter
120 | verticalAlignment: Text.AlignVCenter
121 | }
122 | }
123 |
124 | Image {
125 | id: avatarImage
126 | visible: peerAvatar.length != 0
127 |
128 | anchors.left: parent.left
129 | anchors.verticalCenter: parent.verticalCenter
130 | anchors.leftMargin: avatarRect.anchors.leftMargin
131 |
132 | width: 30 * kgScaling
133 | height: width
134 | smooth: true
135 |
136 | asynchronous: true
137 | source: peerAvatar
138 | }
139 |
140 | Column {
141 | anchors.left: avatarRect.right
142 | anchors.verticalCenter: avatarRect.verticalCenter
143 | anchors.leftMargin: avatarRect.anchors.leftMargin
144 | anchors.right: actionsButton.left
145 |
146 | Row {
147 | anchors.left: parent.left
148 | anchors.right: parent.right
149 | spacing: 4 * kgScaling
150 |
151 | Text {
152 | elide: Text.ElideRight
153 | text: peerTitle
154 | font.bold: true
155 | font.pixelSize: 12 * kgScaling
156 | color: "#FFFFFF"
157 | }
158 | }
159 |
160 | Text {
161 | text: peerTooltip
162 | color: "#FFFFFF"
163 | anchors.left: parent.left
164 | anchors.right: parent.right
165 | elide: Text.ElideRight
166 | font.pixelSize: 12 * kgScaling
167 | }
168 | }
169 |
170 | Item {
171 | id: actionsButton
172 | anchors.top: parent.top
173 | anchors.right: parent.right
174 | anchors.bottom: parent.bottom
175 | width: height
176 |
177 | visible: false
178 |
179 | Image {
180 | id: actionsImage
181 | anchors.centerIn: parent
182 | source: "../../img/dots-vertical.png"
183 | width: 20 * kgScaling
184 | height: height
185 | smooth: true
186 | asynchronous: true
187 | }
188 | }
189 | }
190 |
191 | Text {
192 | id: appNameText
193 | anchors.left: parent.left
194 | width: parent.width
195 | height: parent.height
196 | verticalAlignment: Text.AlignVCenter
197 | text: "Kutegram"
198 | font.bold: true
199 | font.pixelSize: 12 * kgScaling
200 | color: "#FFFFFF"
201 |
202 | MouseArea {
203 | anchors.fill: parent
204 | }
205 | }
206 |
207 | states: [
208 | State {
209 | name: "CHAT"
210 | PropertyChanges {
211 | target: folderList
212 | opacity: 0
213 | anchors.leftMargin: -folderList.width
214 | }
215 | PropertyChanges {
216 | target: peerHeader
217 | opacity: 1
218 | anchors.leftMargin: 0
219 | }
220 | PropertyChanges {
221 | target: appNameText
222 | opacity: 0
223 | anchors.leftMargin: -appNameText.width
224 | }
225 | },
226 | State {
227 | name: "BACK"
228 | PropertyChanges {
229 | target: folderList
230 | opacity: 0
231 | anchors.leftMargin: -folderList.width
232 | }
233 | PropertyChanges {
234 | target: peerHeader
235 | opacity: 0
236 | anchors.leftMargin: -peerHeader.width
237 | }
238 | PropertyChanges {
239 | target: appNameText
240 | opacity: 1
241 | anchors.leftMargin: 0
242 | }
243 | },
244 | State {
245 | name: "MENU"
246 | PropertyChanges {
247 | target: folderList
248 | opacity: 1
249 | anchors.leftMargin: 0
250 | }
251 | PropertyChanges {
252 | target: peerHeader
253 | opacity: 0
254 | anchors.leftMargin: -peerHeader.width
255 | }
256 | PropertyChanges {
257 | target: appNameText
258 | opacity: 0
259 | anchors.leftMargin: -appNameText.width
260 | }
261 | }
262 |
263 | ]
264 |
265 | transitions: [
266 | Transition {
267 | NumberAnimation {
268 | properties: "opacity,anchors.leftMargin"
269 | easing.type: Easing.InOutQuad
270 | duration: 200
271 | }
272 | }
273 | ]
274 | }
275 |
276 | property string currentState: "MENU"
277 |
278 | function menuButtonClicked() {
279 | if (currentState == "MENU") {
280 | currentState = "BACK";
281 | drawer.opened = true;
282 | } else {
283 | currentState = "MENU";
284 | drawer.opened = false;
285 | mainScreen.state = "MENU";
286 | }
287 | }
288 |
289 | Item {
290 | id: menuButton
291 | anchors.top: parent.top
292 | anchors.left: parent.left
293 | anchors.bottom: parent.bottom
294 | width: height
295 | state: currentState == "CHAT" ? "BACK" : currentState
296 |
297 | Image {
298 | id: menuImage
299 | source: "../../img/menu.png"
300 | x: 10 * kgScaling
301 | y: 10 * kgScaling
302 | width: 20 * kgScaling
303 | height: width
304 | smooth: true
305 | asynchronous: true
306 | }
307 |
308 | Image {
309 | id: backImage
310 | source: "../../img/arrow-left.png"
311 | x: 10 * kgScaling
312 | y: 10 * kgScaling
313 | width: 20 * kgScaling
314 | height: width
315 | smooth: true
316 | asynchronous: true
317 | }
318 |
319 | MouseArea {
320 | id: menuButtonArea
321 | anchors.fill: parent
322 | onClicked: {
323 | menuButtonClicked();
324 | }
325 | }
326 |
327 | states: [
328 | State {
329 | name: "MENU"
330 | PropertyChanges {
331 | target: menuImage
332 | opacity: 1
333 | x: 10 * kgScaling
334 | }
335 | PropertyChanges {
336 | target: backImage
337 | opacity: 0
338 | rotation: 180
339 | }
340 | },
341 | State {
342 | name: "BACK"
343 | PropertyChanges {
344 | target: menuImage
345 | opacity: 0
346 | x: 0
347 | }
348 | PropertyChanges {
349 | target: backImage
350 | opacity: 1
351 | rotation: 0
352 | }
353 | }
354 | ]
355 |
356 | transitions: [
357 | Transition {
358 | NumberAnimation {
359 | properties: "opacity,rotation,x"
360 | easing.type: Easing.InOutQuad
361 | duration: 200
362 | }
363 | }
364 | ]
365 | }
366 | }
367 |
--------------------------------------------------------------------------------