├── 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 | --------------------------------------------------------------------------------