summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.clang-format22
-rw-r--r--.gitignore46
-rw-r--r--CMakeLists.txt104
-rw-r--r--LICENSE674
-rw-r--r--forms/ChatPage.ui153
-rw-r--r--forms/MainWindow.ui42
-rw-r--r--forms/RoomList.ui157
-rw-r--r--include/ChatPage.h88
-rw-r--r--include/Deserializable.h52
-rw-r--r--include/HistoryView.h62
-rw-r--r--include/HistoryViewItem.h41
-rw-r--r--include/HistoryViewManager.h47
-rw-r--r--include/InputValidator.h49
-rw-r--r--include/Login.h56
-rw-r--r--include/LoginPage.h80
-rw-r--r--include/MainWindow.h84
-rw-r--r--include/MatrixClient.h134
-rw-r--r--include/Profile.h39
-rw-r--r--include/RegisterPage.h74
-rw-r--r--include/RoomInfo.h49
-rw-r--r--include/RoomInfoListItem.h93
-rw-r--r--include/RoomList.h60
-rw-r--r--include/SlidingStackWidget.h94
-rw-r--r--include/Sync.h119
-rw-r--r--include/TextInputWidget.h53
-rw-r--r--include/TopRoomBar.h79
-rw-r--r--include/UserInfoWidget.h60
-rw-r--r--include/WelcomePage.h61
-rw-r--r--include/ui/Avatar.h51
-rw-r--r--include/ui/Badge.h63
-rw-r--r--include/ui/FlatButton.h210
-rw-r--r--include/ui/OverlayWidget.h23
-rw-r--r--include/ui/RaisedButton.h31
-rw-r--r--include/ui/Ripple.h136
-rw-r--r--include/ui/RippleOverlay.h58
-rw-r--r--include/ui/TextField.h170
-rw-r--r--include/ui/Theme.h89
-rw-r--r--include/ui/ThemeManager.h33
-rw-r--r--resources/fonts/LICENSE.txt202
-rw-r--r--resources/fonts/OpenSans-Bold.ttfbin0 -> 224592 bytes
-rw-r--r--resources/fonts/OpenSans-BoldItalic.ttfbin0 -> 213292 bytes
-rw-r--r--resources/fonts/OpenSans-ExtraBold.ttfbin0 -> 222584 bytes
-rw-r--r--resources/fonts/OpenSans-ExtraBoldItalic.ttfbin0 -> 213420 bytes
-rw-r--r--resources/fonts/OpenSans-Italic.ttfbin0 -> 212896 bytes
-rw-r--r--resources/fonts/OpenSans-Light.ttfbin0 -> 222412 bytes
-rw-r--r--resources/fonts/OpenSans-LightItalic.ttfbin0 -> 213128 bytes
-rw-r--r--resources/fonts/OpenSans-Regular.ttfbin0 -> 217360 bytes
-rw-r--r--resources/fonts/OpenSans-Semibold.ttfbin0 -> 221328 bytes
-rw-r--r--resources/fonts/OpenSans-SemiboldItalic.ttfbin0 -> 212820 bytes
-rw-r--r--resources/icons/add-file.pngbin0 -> 1124 bytes
-rw-r--r--resources/icons/clip-dark.pngbin0 -> 1124 bytes
-rw-r--r--resources/icons/cog.pngbin0 -> 952 bytes
-rw-r--r--resources/icons/left-angle.pngbin0 -> 225 bytes
-rw-r--r--resources/icons/left-chevron.pngbin0 -> 590 bytes
-rw-r--r--resources/icons/plus-symbol.pngbin0 -> 453 bytes
-rw-r--r--resources/icons/search.pngbin0 -> 939 bytes
-rw-r--r--resources/icons/send-button.pngbin0 -> 799 bytes
-rw-r--r--resources/icons/share-dark.pngbin0 -> 799 bytes
-rw-r--r--resources/icons/user-shape.pngbin0 -> 730 bytes
-rw-r--r--resources/res.qrc27
-rw-r--r--src/ChatPage.cc277
-rw-r--r--src/Deserializable.cc31
-rw-r--r--src/HistoryView.cc145
-rw-r--r--src/HistoryViewItem.cc80
-rw-r--r--src/HistoryViewManager.cc83
-rw-r--r--src/InputValidator.cc31
-rw-r--r--src/Login.cc89
-rw-r--r--src/LoginPage.cc147
-rw-r--r--src/MainWindow.cc134
-rw-r--r--src/MatrixClient.cc365
-rw-r--r--src/Profile.cc50
-rw-r--r--src/RegisterPage.cc166
-rw-r--r--src/RoomInfo.cc71
-rw-r--r--src/RoomInfoListItem.cc138
-rw-r--r--src/RoomList.cc119
-rw-r--r--src/SlidingStackWidget.cc151
-rw-r--r--src/Sync.cc290
-rw-r--r--src/TextInputWidget.cc91
-rw-r--r--src/TopRoomBar.cc93
-rw-r--r--src/UserInfoWidget.cc104
-rw-r--r--src/WelcomePage.cc103
-rw-r--r--src/main.cc48
-rw-r--r--src/ui/Avatar.cc143
-rw-r--r--src/ui/Badge.cc186
-rw-r--r--src/ui/FlatButton.cc761
-rw-r--r--src/ui/OverlayWidget.cc59
-rw-r--r--src/ui/RaisedButton.cc92
-rw-r--r--src/ui/Ripple.cc106
-rw-r--r--src/ui/RippleOverlay.cc61
-rw-r--r--src/ui/TextField.cc346
-rw-r--r--src/ui/Theme.cc73
-rw-r--r--src/ui/ThemeManager.cc20
92 files changed, 8418 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..ae3158bd
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,22 @@
+---
+Language: Cpp
+AccessModifierOffset: -8
+AlignAfterOpenBracket: true
+AlignEscapedNewlinesLeft: false
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+BasedOnStyle: Google
+BinPackArguments: false
+BinPackParameters: false
+BreakBeforeBraces: Linux
+BreakConstructorInitializersBeforeComma: true
+ColumnLimit: 0
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ContinuationIndentWidth: 8
+IndentCaseLabels: false
+IndentWidth: 8
+MaxEmptyLinesToKeep: 1
+PointerAlignment: Right
+UseTab: Always
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..252e1804
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,46 @@
+build
+tags
+
+# C++ objects and libs
+
+*.slo
+*.lo
+*.o
+*.a
+*.la
+*.lai
+*.so
+*.dll
+*.dylib
+
+# Qt-es
+
+/.qmake.cache
+/.qmake.stash
+*.pro.user
+*.pro.user.*
+*.qbs.user
+*.qbs.user.*
+*.moc
+CMakeLists.txt.user
+moc_*.cpp
+qrc_*.cpp
+ui_*.h
+Makefile*
+*-build-*
+
+# QtCreator
+
+*.autosave
+
+#QtCtreator Qml
+*.qmlproject.user
+*.qmlproject.user.*
+
+#####=== CMake ===#####
+
+CMakeCache.txt
+CMakeFiles
+cmake_install.cmake
+install_manifest.txt
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..e7d52b2f
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,104 @@
+cmake_minimum_required(VERSION 3.1)
+
+project(nheko CXX)
+
+set(nheko_VERSION_MAJOR 0)
+set(nheko_VERSION_MINOR 1)
+set(nheko_VERSION_PATCH 0)
+
+find_package(Qt5Widgets REQUIRED)
+find_package(Qt5Network REQUIRED)
+
+set(CMAKE_C_COMPILER gcc)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_CXX_FLAGS
+    "${CMAKE_CXX_FLAGS} \
+    -std=gnu++11 \
+    -Wall \
+    -Wextra \
+    -Werror \
+    -pedantic")
+
+set(SRC_FILES
+    src/ChatPage.cc
+    src/Deserializable.cc
+    src/HistoryView.cc
+    src/HistoryViewItem.cc
+    src/HistoryViewManager.cc
+    src/InputValidator.cc
+    src/Login.cc
+    src/LoginPage.cc
+    src/MainWindow.cc
+    src/MatrixClient.cc
+    src/Profile.cc
+    src/RoomInfo.cc
+    src/RoomInfoListItem.cc
+    src/RoomList.cc
+    src/RegisterPage.cc
+    src/SlidingStackWidget.cc
+    src/Sync.cc
+    src/TextInputWidget.cc
+    src/TopRoomBar.cc
+    src/UserInfoWidget.cc
+    src/WelcomePage.cc
+    src/main.cc
+
+    src/ui/Avatar.cc
+    src/ui/Badge.cc
+    src/ui/FlatButton.cc
+    src/ui/RaisedButton.cc
+    src/ui/Ripple.cc
+    src/ui/RippleOverlay.cc
+    src/ui/OverlayWidget.cc
+    src/ui/TextField.cc
+    src/ui/Theme.cc
+    src/ui/ThemeManager.cc
+)
+
+include_directories(include)
+include_directories(include/ui)
+
+qt5_wrap_ui (UI_HEADERS
+    forms/ChatPage.ui
+    forms/MainWindow.ui
+    forms/RoomList.ui
+)
+
+qt5_wrap_cpp(MOC_HEADERS
+    include/ChatPage.h
+    include/HistoryView.h
+    include/HistoryViewItem.h
+    include/HistoryViewManager.h
+    include/LoginPage.h
+    include/MainWindow.h
+    include/MatrixClient.h
+    include/RegisterPage.h
+    include/RoomInfoListItem.h
+    include/RoomList.h
+    include/UserInfoWidget.h
+    include/SlidingStackWidget.h
+    include/TopRoomBar.h
+    include/TextInputWidget.h
+    include/WelcomePage.h
+
+    include/ui/Avatar.h
+    include/ui/Badge.h
+    include/ui/FlatButton.h
+    include/ui/OverlayWidget.h
+    include/ui/RaisedButton.h
+    include/ui/Ripple.h
+    include/ui/RippleOverlay.h
+    include/ui/TextField.h
+    include/ui/Theme.h
+    include/ui/ThemeManager.h
+)
+
+qt5_add_resources(QRC resources/res.qrc)
+
+add_executable (nheko ${SRC_FILES} ${UI_HEADERS} ${MOC_HEADERS} ${QRC})
+target_link_libraries (nheko Qt5::Widgets Qt5::Network)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..63c30502
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    {{ project }}  Copyright (C) {{ year }}  {{ organization }}
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/forms/ChatPage.ui b/forms/ChatPage.ui
new file mode 100644
index 00000000..9d981b22
--- /dev/null
+++ b/forms/ChatPage.ui
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ChatPage</class>
+ <widget class="QWidget" name="ChatPage">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>798</width>
+    <height>519</height>
+   </rect>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>0</width>
+    <height>0</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <property name="styleSheet">
+   <string notr="true">background-color: #171919;
+</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <item>
+      <layout class="QVBoxLayout" name="sideBarLayout">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QWidget" name="sideBarWidget" native="true">
+         <property name="minimumSize">
+          <size>
+           <width>300</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>300</width>
+           <height>16777215</height>
+          </size>
+         </property>
+         <layout class="QVBoxLayout" name="verticalLayout_8">
+          <property name="spacing">
+           <number>0</number>
+          </property>
+          <property name="leftMargin">
+           <number>0</number>
+          </property>
+          <property name="topMargin">
+           <number>0</number>
+          </property>
+          <property name="rightMargin">
+           <number>0</number>
+          </property>
+          <property name="bottomMargin">
+           <number>0</number>
+          </property>
+          <item>
+           <layout class="QVBoxLayout" name="sideBarTopLayout">
+            <property name="spacing">
+             <number>0</number>
+            </property>
+            <item>
+             <widget class="QWidget" name="sideBarTopWidget" native="true">
+              <property name="styleSheet">
+               <string notr="true">background-color: #1c3133;
+color: #ebebeb;</string>
+              </property>
+              <layout class="QVBoxLayout" name="verticalLayout_9">
+               <property name="spacing">
+                <number>0</number>
+               </property>
+               <item>
+                <layout class="QVBoxLayout" name="sideBarTopUserInfoLayout">
+                 <property name="spacing">
+                  <number>0</number>
+                 </property>
+                </layout>
+               </item>
+              </layout>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <layout class="QVBoxLayout" name="sideBarMainLayout">
+            <property name="spacing">
+             <number>0</number>
+            </property>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <layout class="QVBoxLayout" name="contentLayout">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="topBarLayout">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="mainContentLayout">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+        </layout>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../resources/res.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/forms/MainWindow.ui b/forms/MainWindow.ui
new file mode 100644
index 00000000..7035b213
--- /dev/null
+++ b/forms/MainWindow.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>850</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>850</width>
+    <height>600</height>
+   </size>
+  </property>
+  <property name="font">
+   <font>
+    <family>Open Sans</family>
+    <stylestrategy>PreferAntialias</stylestrategy>
+   </font>
+  </property>
+  <property name="windowTitle">
+   <string notr="true">nheko - Matrix Desktop Client </string>
+  </property>
+  <property name="styleSheet">
+   <string notr="true">background-color: #f9f9f9</string>
+  </property>
+  <widget class="QWidget" name="centralWidget"/>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/forms/RoomList.ui b/forms/RoomList.ui
new file mode 100644
index 00000000..0d65fbf5
--- /dev/null
+++ b/forms/RoomList.ui
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RoomList</class>
+ <widget class="QWidget" name="RoomList">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>423</width>
+    <height>500</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>0</width>
+    <height>500</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <property name="styleSheet">
+   <string notr="true">QWidget {
+background-color: #171919;
+color: #ebebeb;
+}
+
+QScrollBar:vertical {               
+     background-color: #171919;
+     width: 10px;
+	border-radius: 20px;
+	margin: 0px 2px 0 2px;
+}
+
+QScrollBar::handle:vertical {
+	border-radius: 50px;
+	background-color: #1c3133;
+}
+
+QScrollBar::add-line:vertical {
+      border: none;
+      background: none;
+}
+
+QScrollBar::sub-line:vertical {
+      border: none;
+      background: none;
+}</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <item>
+      <widget class="QScrollArea" name="scrollArea">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>0</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>16777215</width>
+         <height>16777215</height>
+        </size>
+       </property>
+       <property name="horizontalScrollBarPolicy">
+        <enum>Qt::ScrollBarAlwaysOff</enum>
+       </property>
+       <property name="sizeAdjustPolicy">
+        <enum>QAbstractScrollArea::AdjustToContents</enum>
+       </property>
+       <property name="widgetResizable">
+        <bool>true</bool>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+       </property>
+       <widget class="QWidget" name="scrollAreaWidgetContents">
+        <property name="geometry">
+         <rect>
+          <x>0</x>
+          <y>0</y>
+          <width>419</width>
+          <height>496</height>
+         </rect>
+        </property>
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <layout class="QVBoxLayout" name="verticalLayout_4">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <layout class="QVBoxLayout" name="scrollVerticalLayout">
+           <property name="spacing">
+            <number>0</number>
+           </property>
+          </layout>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/include/ChatPage.h b/include/ChatPage.h
new file mode 100644
index 00000000..c3fa6bf6
--- /dev/null
+++ b/include/ChatPage.h
@@ -0,0 +1,88 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHATPAGE_H
+#define CHATPAGE_H
+
+#include <QByteArray>
+#include <QNetworkAccessManager>
+#include <QPixmap>
+#include <QTimer>
+#include <QWidget>
+
+#include "HistoryViewManager.h"
+#include "MatrixClient.h"
+#include "RoomInfo.h"
+#include "RoomList.h"
+#include "TextInputWidget.h"
+#include "TopRoomBar.h"
+#include "UserInfoWidget.h"
+
+namespace Ui
+{
+class ChatPage;
+}
+
+class ChatPage : public QWidget
+{
+	Q_OBJECT
+
+public:
+	explicit ChatPage(QWidget *parent = 0);
+	~ChatPage();
+
+	// Initialize all the components of the UI.
+	void bootstrap(QString userid, QString homeserver, QString token);
+
+public slots:
+	// Updates the user info box.
+	void updateOwnProfileInfo(QUrl avatar_url, QString display_name);
+	void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
+	void initialSyncCompleted(SyncResponse response);
+	void syncCompleted(SyncResponse response);
+	void changeTopRoomInfo(const RoomInfo &info);
+	void sendTextMessage(const QString &msg);
+	void messageSent(const QString event_id, int txn_id);
+	void startSync();
+
+private:
+	Ui::ChatPage *ui;
+
+	void setOwnAvatar(QByteArray img);
+
+	RoomList *room_list_;
+	HistoryViewManager *view_manager_;
+
+	TopRoomBar *top_bar_;
+	TextInputWidget *text_input_;
+
+	QTimer *sync_timer_;
+	int sync_interval_;
+
+	RoomInfo current_room_;
+	QMap<QString, QPixmap> room_avatars_;
+
+	UserInfoWidget *user_info_widget_;
+
+	// Matrix client
+	MatrixClient *matrix_client_;
+
+	// Used for one off media requests.
+	QNetworkAccessManager *content_downloader_;
+};
+
+#endif  // CHATPAGE_H
diff --git a/include/Deserializable.h b/include/Deserializable.h
new file mode 100644
index 00000000..6a9b4cd5
--- /dev/null
+++ b/include/Deserializable.h
@@ -0,0 +1,52 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DESERIALIZABLE_H
+#define DESERIALIZABLE_H
+
+#include <exception>
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonValue>
+
+class DeserializationException : public std::exception
+{
+public:
+	explicit DeserializationException(const std::string &msg);
+	virtual const char *what() const throw();
+
+private:
+	std::string msg_;
+};
+
+// JSON response structs need to implement the interface.
+class Deserializable
+{
+public:
+	virtual void deserialize(QJsonValue) throw(DeserializationException)
+	{
+	}
+	virtual void deserialize(QJsonObject) throw(DeserializationException)
+	{
+	}
+	virtual void deserialize(QJsonDocument) throw(DeserializationException)
+	{
+	}
+};
+
+#endif
diff --git a/include/HistoryView.h b/include/HistoryView.h
new file mode 100644
index 00000000..9266d6ac
--- /dev/null
+++ b/include/HistoryView.h
@@ -0,0 +1,62 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HISTORY_VIEW_H
+#define HISTORY_VIEW_H
+
+#include <QHBoxLayout>
+#include <QList>
+#include <QScrollArea>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#include "HistoryViewItem.h"
+#include "Sync.h"
+
+class HistoryView : public QWidget
+{
+	Q_OBJECT
+
+public:
+	explicit HistoryView(QWidget *parent = 0);
+	explicit HistoryView(QList<Event> events, QWidget *parent = 0);
+	~HistoryView();
+
+	void addHistoryItem(Event event, QString color, bool with_sender);
+	void addEvents(const QList<Event> &events);
+
+public slots:
+	void sliderRangeChanged(int min, int max);
+
+private:
+	static const QList<QString> COLORS;
+
+	void init();
+
+	QString chooseRandomColor();
+
+	QVBoxLayout *top_layout_;
+	QVBoxLayout *scroll_layout_;
+
+	QScrollArea *scroll_area_;
+	QWidget *scroll_widget_;
+
+	QString last_sender_;
+	QMap<QString, QString> nick_colors_;
+};
+
+#endif  // HISTORY_VIEW_H
diff --git a/include/HistoryViewItem.h b/include/HistoryViewItem.h
new file mode 100644
index 00000000..b817194b
--- /dev/null
+++ b/include/HistoryViewItem.h
@@ -0,0 +1,41 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HISTORY_VIEW_ITEM_H
+#define HISTORY_VIEW_ITEM_H
+
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QWidget>
+
+#include "Sync.h"
+
+class HistoryViewItem : public QWidget
+{
+	Q_OBJECT
+public:
+	HistoryViewItem(Event event, bool with_sender, QString color, QWidget *parent = 0);
+	~HistoryViewItem();
+
+private:
+	QHBoxLayout *top_layout_;
+
+	QLabel *time_label_;
+	QLabel *content_label_;
+};
+
+#endif  // HISTORY_VIEW_ITEM_H
diff --git a/include/HistoryViewManager.h b/include/HistoryViewManager.h
new file mode 100644
index 00000000..8405d005
--- /dev/null
+++ b/include/HistoryViewManager.h
@@ -0,0 +1,47 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HISTORY_VIEW_MANAGER_H
+#define HISTORY_VIEW_MANAGER_H
+
+#include <QDebug>
+#include <QStackedWidget>
+#include <QWidget>
+
+#include "HistoryView.h"
+#include "RoomInfo.h"
+#include "Sync.h"
+
+class HistoryViewManager : public QStackedWidget
+{
+	Q_OBJECT
+
+public:
+	HistoryViewManager(QWidget *parent);
+	~HistoryViewManager();
+
+	void initialize(const Rooms &rooms);
+	void sync(const Rooms &rooms);
+
+public slots:
+	void setHistoryView(const RoomInfo &info);
+
+private:
+	QMap<QString, HistoryView *> views_;
+};
+
+#endif
diff --git a/include/InputValidator.h b/include/InputValidator.h
new file mode 100644
index 00000000..feeaf70a
--- /dev/null
+++ b/include/InputValidator.h
@@ -0,0 +1,49 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MATRIXIDVALIDATOR_H
+#define MATRIXIDVALIDATOR_H
+
+#include <QRegExp>
+#include <QRegExpValidator>
+
+class InputValidator
+{
+public:
+	InputValidator(QObject *parent = 0);
+
+	// Validators for the different types of input.
+	QRegExpValidator *id_;
+	QRegExpValidator *localpart_;
+	QRegExpValidator *password_;
+	QRegExpValidator *domain_;
+
+private:
+	// Regular expression used to validate the whole matrix id.
+	const QRegExp matrix_id_;
+
+	// Regular expressino to validate the matrix localpart.
+	const QRegExp matrix_localpart_;
+
+	// Regular expression to validate a password for a matrix account.
+	const QRegExp matrix_password_;
+
+	// Regular expression to validate a domain name.
+	const QRegExp server_domain_;
+};
+
+#endif  // MATRIXIDVALIDATOR_H
diff --git a/include/Login.h b/include/Login.h
new file mode 100644
index 00000000..857be14b
--- /dev/null
+++ b/include/Login.h
@@ -0,0 +1,56 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOGIN_H
+#define LOGIN_H
+
+#include <QJsonDocument>
+
+#include "Deserializable.h"
+
+class LoginRequest
+{
+public:
+	LoginRequest();
+	LoginRequest(QString username, QString password);
+
+	QByteArray serialize();
+
+	void setPassword(QString password);
+	void setUser(QString username);
+
+private:
+	QString user_;
+	QString password_;
+};
+
+class LoginResponse : public Deserializable
+{
+public:
+	void deserialize(QJsonDocument data) throw(DeserializationException) override;
+
+	QString getAccessToken();
+	QString getHomeServer();
+	QString getUserId();
+
+private:
+	QString access_token_;
+	QString home_server_;
+	QString user_id_;
+};
+
+#endif  // LOGIN_H
diff --git a/include/LoginPage.h b/include/LoginPage.h
new file mode 100644
index 00000000..d6b57efb
--- /dev/null
+++ b/include/LoginPage.h
@@ -0,0 +1,80 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOGINPAGE_H
+#define LOGINPAGE_H
+
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#include "FlatButton.h"
+#include "InputValidator.h"
+#include "RaisedButton.h"
+#include "TextField.h"
+
+class LoginPage : public QWidget
+{
+	Q_OBJECT
+
+public:
+	explicit LoginPage(QWidget *parent = 0);
+	~LoginPage();
+
+signals:
+	void backButtonClicked();
+
+	// Emitted after the matrix ID validation. The handler should be
+	// responsible for performing the actual login with a remote server.
+	void userLogin(const QString &username, const QString &password, const QString home_server);
+
+public slots:
+	// Displays errors produced during the login.
+	void loginError(QString error_message);
+
+private slots:
+	// Callback for the back button.
+	void onBackButtonClicked();
+
+	// Callback for the login button.
+	void onLoginButtonClicked();
+
+private:
+	QVBoxLayout *top_layout_;
+
+	QHBoxLayout *back_layout_;
+	QHBoxLayout *logo_layout_;
+	QHBoxLayout *button_layout_;
+
+	QLabel *logo_;
+	QLabel *error_label_;
+
+	FlatButton *back_button_;
+	RaisedButton *login_button_;
+
+	QWidget *form_widget_;
+	QHBoxLayout *form_wrapper_;
+	QVBoxLayout *form_layout_;
+
+	TextField *username_input_;
+	TextField *password_input_;
+
+	InputValidator *matrix_id_validator_;
+};
+
+#endif  // LOGINPAGE_H
diff --git a/include/MainWindow.h b/include/MainWindow.h
new file mode 100644
index 00000000..dbbda3f2
--- /dev/null
+++ b/include/MainWindow.h
@@ -0,0 +1,84 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+#include "ChatPage.h"
+#include "LoginPage.h"
+#include "MatrixClient.h"
+#include "RegisterPage.h"
+#include "SlidingStackWidget.h"
+#include "WelcomePage.h"
+
+namespace Ui
+{
+class MainWindow;
+}
+
+class MainWindow : public QMainWindow
+{
+	Q_OBJECT
+
+public:
+	explicit MainWindow(QWidget *parent = 0);
+	~MainWindow();
+
+public slots:
+	// Show the welcome page in the main window.
+	void showWelcomePage();
+
+	// Show the login page in the main window.
+	void showLoginPage();
+
+	// Show the register page in the main window.
+	void showRegisterPage();
+
+	// Show the chat page and start communicating with the given access token.
+	void showChatPage(QString user_id, QString home_server, QString token);
+
+	// Performs the actual login.
+	void matrixLogin(const QString &username, const QString &password, const QString &home_server);
+
+	// Performs the actual registration.
+	void matrixRegister(const QString &username, const QString &password, const QString &server);
+
+private:
+	// The UI component of the main window.
+	Ui::MainWindow *ui_;
+
+	// The initial welcome screen.
+	WelcomePage *welcome_page_;
+
+	// The login screen.
+	LoginPage *login_page_;
+
+	// The register page.
+	RegisterPage *register_page_;
+
+	// A stacked widget that handles the transitions between widgets.
+	SlidingStackWidget *sliding_stack_;
+
+	// The main chat area.
+	ChatPage *chat_page_;
+
+	MatrixClient *matrix_client_;
+};
+
+#endif  // MAINWINDOW_H
diff --git a/include/MatrixClient.h b/include/MatrixClient.h
new file mode 100644
index 00000000..46d6cc5b
--- /dev/null
+++ b/include/MatrixClient.h
@@ -0,0 +1,134 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MATRIXCLIENT_H
+#define MATRIXCLIENT_H
+
+#include <QtNetwork/QNetworkAccessManager>
+
+#include "Profile.h"
+#include "Sync.h"
+
+/*
+ * MatrixClient provides the high level API to communicate with
+ * a Matrix homeserver. All the responses are returned through signals.
+ */
+class MatrixClient : public QNetworkAccessManager
+{
+	Q_OBJECT
+public:
+	MatrixClient(QString server, QObject *parent = 0);
+	~MatrixClient();
+
+	// Client API.
+	void initialSync();
+	void sync();
+	void sendTextMessage(QString roomid, QString msg);
+	void login(const QString &username, const QString &password);
+	void registerUser(const QString &username, const QString &password);
+	void versions();
+
+	inline QString getHomeServer();
+	inline void incrementTransactionId();
+
+public slots:
+	// Profile
+	void getOwnProfile();
+
+	inline void setServer(QString server);
+	inline void setAccessToken(QString token);
+	inline void setNextBatchToken(const QString &next_batch);
+
+signals:
+	// Emitted after a error during the login.
+	void loginError(QString error);
+
+	// Emitted after succesfull user login. A new access token is returned by the server.
+	void loginSuccess(QString user_id, QString home_server, QString token);
+
+	// Returned profile data for the user's account.
+	void getOwnProfileResponse(QUrl avatar_url, QString display_name);
+	void initialSyncCompleted(SyncResponse response);
+	void syncCompleted(SyncResponse response);
+	void messageSent(QString event_id, int txn_id);
+
+private slots:
+	void onResponse(QNetworkReply *reply);
+
+private:
+	enum Endpoint {
+		GetOwnProfile,
+		GetProfile,
+		InitialSync,
+		Login,
+		Register,
+		SendTextMessage,
+		Sync,
+		Versions,
+	};
+
+	// Response handlers.
+	void onLoginResponse(QNetworkReply *reply);
+	void onRegisterResponse(QNetworkReply *reply);
+	void onVersionsResponse(QNetworkReply *reply);
+	void onGetOwnProfileResponse(QNetworkReply *reply);
+	void onSendTextMessageResponse(QNetworkReply *reply);
+	void onInitialSyncResponse(QNetworkReply *reply);
+	void onSyncResponse(QNetworkReply *reply);
+
+	// Client API prefix.
+	QString api_url_;
+
+	// The Matrix server used for communication.
+	QString server_;
+
+	// The access token used for authentication.
+	QString token_;
+
+	// Increasing transaction ID.
+	int txn_id_;
+
+	// Token to be used for the next sync.
+	QString next_batch_;
+};
+
+inline QString MatrixClient::getHomeServer()
+{
+	return server_;
+}
+
+inline void MatrixClient::setServer(QString server)
+{
+	server_ = "https://" + server;
+}
+
+inline void MatrixClient::setAccessToken(QString token)
+{
+	token_ = token;
+}
+
+inline void MatrixClient::setNextBatchToken(const QString &next_batch)
+{
+	next_batch_ = next_batch;
+}
+
+inline void MatrixClient::incrementTransactionId()
+{
+	txn_id_ += 1;
+}
+
+#endif  // MATRIXCLIENT_H
diff --git a/include/Profile.h b/include/Profile.h
new file mode 100644
index 00000000..a36393ec
--- /dev/null
+++ b/include/Profile.h
@@ -0,0 +1,39 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PROFILE_H
+#define PROFILE_H
+
+#include <QJsonDocument>
+#include <QUrl>
+
+#include "Deserializable.h"
+
+class ProfileResponse : public Deserializable
+{
+public:
+	void deserialize(QJsonDocument data) throw(DeserializationException) override;
+
+	QUrl getAvatarUrl();
+	QString getDisplayName();
+
+private:
+	QUrl avatar_url_;
+	QString display_name_;
+};
+
+#endif  // PROFILE_H
diff --git a/include/RegisterPage.h b/include/RegisterPage.h
new file mode 100644
index 00000000..f1c3848e
--- /dev/null
+++ b/include/RegisterPage.h
@@ -0,0 +1,74 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef REGISTERPAGE_H
+#define REGISTERPAGE_H
+
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#include "FlatButton.h"
+#include "InputValidator.h"
+#include "RaisedButton.h"
+#include "TextField.h"
+
+class RegisterPage : public QWidget
+{
+	Q_OBJECT
+
+public:
+	explicit RegisterPage(QWidget *parent = 0);
+	~RegisterPage();
+
+signals:
+	void backButtonClicked();
+
+	// Emitted after successful input validation. The handler should be
+	// responsible for the actual registering on the remote Matrix server.
+	void registerUser(const QString &username, const QString &password, const QString &server);
+
+private slots:
+	void onBackButtonClicked();
+	void onRegisterButtonClicked();
+
+private:
+	QVBoxLayout *top_layout_;
+
+	QHBoxLayout *back_layout_;
+	QHBoxLayout *logo_layout_;
+	QHBoxLayout *button_layout_;
+
+	QLabel *logo_;
+
+	FlatButton *back_button_;
+	RaisedButton *register_button_;
+
+	QWidget *form_widget_;
+	QHBoxLayout *form_wrapper_;
+	QVBoxLayout *form_layout_;
+
+	TextField *username_input_;
+	TextField *password_input_;
+	TextField *password_confirmation_;
+	TextField *server_input_;
+
+	InputValidator *validator_;
+};
+
+#endif  // REGISTERPAGE_H
diff --git a/include/RoomInfo.h b/include/RoomInfo.h
new file mode 100644
index 00000000..9976ba8a
--- /dev/null
+++ b/include/RoomInfo.h
@@ -0,0 +1,49 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ROOM_INFO_H
+#define ROOM_INFO_H
+
+#include <QList>
+#include <QString>
+#include <QUrl>
+
+class RoomInfo
+{
+public:
+	RoomInfo();
+	RoomInfo(QString name, QString topic = "", QUrl avatar_url = QUrl(""));
+
+	QString id() const;
+	QString name() const;
+	QString topic() const;
+	QUrl avatarUrl() const;
+
+	void setAvatarUrl(const QUrl &url);
+	void setId(const QString &id);
+	void setName(const QString &name);
+	void setTopic(const QString &name);
+
+private:
+	QString id_;
+	QString name_;
+	QString topic_;
+	QUrl avatar_url_;
+	QList<QString> aliases_;
+};
+
+#endif  // ROOM_INFO_H
diff --git a/include/RoomInfoListItem.h b/include/RoomInfoListItem.h
new file mode 100644
index 00000000..4eabfa23
--- /dev/null
+++ b/include/RoomInfoListItem.h
@@ -0,0 +1,93 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ROOMINFOLISTITEM_H
+#define ROOMINFOLISTITEM_H
+
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QWidget>
+
+#include "Avatar.h"
+#include "RippleOverlay.h"
+#include "RoomInfo.h"
+
+class RoomInfoListItem : public QWidget
+{
+	Q_OBJECT
+
+public:
+	RoomInfoListItem(RoomInfo info, QWidget *parent = 0);
+	~RoomInfoListItem();
+
+	inline bool isPressed();
+	inline RoomInfo info();
+	inline void setAvatar(const QImage &avatar_image);
+
+signals:
+	void clicked(RoomInfo info_);
+
+public slots:
+	void setPressedState(bool state);
+
+protected:
+	void mousePressEvent(QMouseEvent *event) override;
+
+private:
+	void setElidedText(QLabel *label, QString text, int width);
+
+	RippleOverlay *ripple_overlay_;
+
+	RoomInfo info_;
+
+	QHBoxLayout *topLayout_;
+
+	QVBoxLayout *avatarLayout_;
+	QVBoxLayout *textLayout_;
+
+	QWidget *avatarWidget_;
+	QWidget *textWidget_;
+
+	QLabel *roomName_;
+	QLabel *roomTopic_;
+
+	Avatar *roomAvatar_;
+
+	QString pressed_style_;
+	QString normal_style_;
+
+	bool is_pressed_;
+	int max_height_;
+};
+
+inline bool RoomInfoListItem::isPressed()
+{
+	return is_pressed_;
+}
+
+inline RoomInfo RoomInfoListItem::info()
+{
+	return info_;
+}
+
+inline void RoomInfoListItem::setAvatar(const QImage &avatar_image)
+{
+	roomAvatar_->setImage(avatar_image);
+}
+
+#endif  // ROOMINFOLISTITEM_H
diff --git a/include/RoomList.h b/include/RoomList.h
new file mode 100644
index 00000000..d679c785
--- /dev/null
+++ b/include/RoomList.h
@@ -0,0 +1,60 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ROOMLIST_H
+#define ROOMLIST_H
+
+#include <QImage>
+#include <QUrl>
+#include <QWidget>
+
+#include "RoomInfo.h"
+#include "RoomInfoListItem.h"
+#include "Sync.h"
+
+namespace Ui
+{
+class RoomList;
+}
+
+class RoomList : public QWidget
+{
+	Q_OBJECT
+
+public:
+	explicit RoomList(QWidget *parent = 0);
+	~RoomList();
+
+	void appendRoom(QString name);
+	void setInitialRooms(const Rooms &rooms);
+	void updateRoomAvatar(const QString &roomid, const QImage &avatar_image);
+	RoomInfo extractRoomInfo(const State &room_state);
+
+signals:
+	void roomChanged(const RoomInfo &info);
+	void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
+
+public slots:
+	void highlightSelectedRoom(const RoomInfo &info);
+
+private:
+	Ui::RoomList *ui;
+
+	QMap<QString, RoomInfoListItem *> available_rooms_;
+};
+
+#endif  // ROOMLIST_H
diff --git a/include/SlidingStackWidget.h b/include/SlidingStackWidget.h
new file mode 100644
index 00000000..7686c6eb
--- /dev/null
+++ b/include/SlidingStackWidget.h
@@ -0,0 +1,94 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SLIDINGSTACKWIDGET_H
+#define SLIDINGSTACKWIDGET_H
+
+#include <QDebug>
+#include <QEasingCurve>
+#include <QParallelAnimationGroup>
+#include <QPropertyAnimation>
+#include <QStackedWidget>
+#include <QWidget>
+
+/*
+ * SlidingStackWidget allows smooth side shifting of widgets,
+ * in addition to the hard switching from one to another offered
+ * by QStackedWidget.
+ */
+
+class SlidingStackWidget : public QStackedWidget
+{
+	Q_OBJECT
+
+public:
+	// Defines the animation direction.
+	enum AnimationDirection {
+		LEFT_TO_RIGHT,
+		RIGHT_TO_LEFT,
+		AUTOMATIC
+	};
+
+	SlidingStackWidget(QWidget *parent);
+	~SlidingStackWidget();
+
+public slots:
+	// Move to the next widget.
+	void slideInNext();
+
+	// Move to the previous widget.
+	void slideInPrevious();
+
+	// Move to a widget by index.
+	void slideInIndex(int index, enum AnimationDirection direction = AnimationDirection::AUTOMATIC);
+
+	int getWidgetIndex(QWidget *widget);
+signals:
+	// Internal signal to alert the engine for the animation's end.
+	void animationFinished();
+
+protected slots:
+	// Internal slot to handle the end of the animation.
+	void onAnimationFinished();
+
+protected:
+	// The method that does the main work for the widget transition.
+	void slideInWidget(QWidget *widget, enum AnimationDirection direction = AnimationDirection::AUTOMATIC);
+
+	// Indicates whether or not the animation is active.
+	bool active_;
+
+	// The widget currently displayed.
+	QWidget *window_;
+
+	// The speed of the animation in milliseconds.
+	int speed_;
+
+	// The animation type.
+	enum QEasingCurve::Type animation_type_;
+
+	// Current widget's index.
+	int now_;
+
+	// Reference point.
+	QPoint current_position_;
+
+	// Next widget's to show index.
+	int next_;
+};
+
+#endif  // SLIDINGSTACKWIDGET_H
diff --git a/include/Sync.h b/include/Sync.h
new file mode 100644
index 00000000..110e8a6e
--- /dev/null
+++ b/include/Sync.h
@@ -0,0 +1,119 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SYNC_H
+#define SYNC_H
+
+#include <QJsonDocument>
+#include <QMap>
+#include <QString>
+
+#include "Deserializable.h"
+
+class Event : public Deserializable
+{
+public:
+	QJsonObject content() const;
+	QJsonObject unsigned_content() const;
+
+	QString sender() const;
+	QString state_key() const;
+	QString type() const;
+	QString eventId() const;
+
+	uint64_t timestamp() const;
+
+	void deserialize(QJsonValue data) throw(DeserializationException) override;
+
+private:
+	QJsonObject content_;
+	QJsonObject unsigned_;
+
+	QString sender_;
+	QString state_key_;
+	QString type_;
+	QString event_id_;
+
+	uint64_t origin_server_ts_;
+};
+
+class State : public Deserializable
+{
+public:
+	void deserialize(QJsonValue data) throw(DeserializationException) override;
+	QList<Event> events() const;
+
+private:
+	QList<Event> events_;
+};
+
+class Timeline : public Deserializable
+{
+public:
+	QList<Event> events() const;
+	QString previousBatch() const;
+	bool limited() const;
+
+	void deserialize(QJsonValue data) throw(DeserializationException) override;
+
+private:
+	QList<Event> events_;
+	QString prev_batch_;
+	bool limited_;
+};
+
+// TODO: Add support for ehpmeral, account_data, undread_notifications
+class JoinedRoom : public Deserializable
+{
+public:
+	State state() const;
+	Timeline timeline() const;
+
+	void deserialize(QJsonValue data) throw(DeserializationException) override;
+
+private:
+	State state_;
+	Timeline timeline_;
+	/* Ephemeral ephemeral_; */
+	/* AccountData account_data_; */
+	/* UnreadNotifications unread_notifications_; */
+};
+
+// TODO: Add support for invited and left rooms.
+class Rooms : public Deserializable
+{
+public:
+	QMap<QString, JoinedRoom> join() const;
+	void deserialize(QJsonValue data) throw(DeserializationException) override;
+
+private:
+	QMap<QString, JoinedRoom> join_;
+};
+
+class SyncResponse : public Deserializable
+{
+public:
+	void deserialize(QJsonDocument data) throw(DeserializationException) override;
+	QString nextBatch() const;
+	Rooms rooms() const;
+
+private:
+	QString next_batch_;
+	Rooms rooms_;
+};
+
+#endif  // SYNC_H
diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h
new file mode 100644
index 00000000..35a12892
--- /dev/null
+++ b/include/TextInputWidget.h
@@ -0,0 +1,53 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEXT_INPUT_WIDGET_H
+#define TEXT_INPUT_WIDGET_H
+
+#include <QHBoxLayout>
+#include <QLineEdit>
+#include <QPaintEvent>
+#include <QWidget>
+
+#include "FlatButton.h"
+
+class TextInputWidget : public QWidget
+{
+	Q_OBJECT
+
+public:
+	TextInputWidget(QWidget *parent = 0);
+	~TextInputWidget();
+
+public slots:
+	void onSendButtonClicked();
+
+signals:
+	void sendTextMessage(QString msg);
+
+protected:
+	void paintEvent(QPaintEvent *event) override;
+
+private:
+	QHBoxLayout *top_layout_;
+	QLineEdit *input_;
+
+	FlatButton *send_file_button_;
+	FlatButton *send_message_button_;
+};
+
+#endif  // TEXT_INPUT_WIDGET_H
diff --git a/include/TopRoomBar.h b/include/TopRoomBar.h
new file mode 100644
index 00000000..247cb9a2
--- /dev/null
+++ b/include/TopRoomBar.h
@@ -0,0 +1,79 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TOP_ROOM_BAR_H
+#define TOP_ROOM_BAR_H
+
+#include <QIcon>
+#include <QImage>
+#include <QLabel>
+#include <QPaintEvent>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#include "Avatar.h"
+#include "FlatButton.h"
+
+class TopRoomBar : public QWidget
+{
+	Q_OBJECT
+public:
+	TopRoomBar(QWidget *parent = 0);
+	~TopRoomBar();
+
+	inline void updateRoomAvatar(const QImage &avatar_image);
+	inline void updateRoomAvatar(const QIcon &icon);
+	inline void updateRoomName(const QString &name);
+	inline void updateRoomTopic(const QString &topic);
+
+protected:
+	void paintEvent(QPaintEvent *event) override;
+
+private:
+	QHBoxLayout *top_layout_;
+	QVBoxLayout *text_layout_;
+
+	QLabel *name_label_;
+	QLabel *topic_label_;
+
+	FlatButton *search_button_;
+	FlatButton *settings_button_;
+
+	Avatar *avatar_;
+};
+
+inline void TopRoomBar::updateRoomAvatar(const QImage &avatar_image)
+{
+	avatar_->setImage(avatar_image);
+}
+
+inline void TopRoomBar::updateRoomAvatar(const QIcon &icon)
+{
+	avatar_->setIcon(icon);
+}
+
+inline void TopRoomBar::updateRoomName(const QString &name)
+{
+	name_label_->setText(name);
+}
+
+inline void TopRoomBar::updateRoomTopic(const QString &topic)
+{
+	topic_label_->setText(topic);
+}
+
+#endif  // TOP_ROOM_BAR_H
diff --git a/include/UserInfoWidget.h b/include/UserInfoWidget.h
new file mode 100644
index 00000000..8cd4b765
--- /dev/null
+++ b/include/UserInfoWidget.h
@@ -0,0 +1,60 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef USER_INFO_WIDGET_H
+#define USER_INFO_WIDGET_H
+
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QWidget>
+
+#include "Avatar.h"
+#include "FlatButton.h"
+
+class UserInfoWidget : public QWidget
+{
+	Q_OBJECT
+
+public:
+	UserInfoWidget(QWidget *parent = 0);
+	~UserInfoWidget();
+
+	void setAvatar(const QImage &img);
+	void setDisplayName(const QString &name);
+	void setUserId(const QString &userid);
+
+private:
+	Avatar *userAvatar_;
+
+	QHBoxLayout *topLayout_;
+	QHBoxLayout *avatarLayout_;
+	QVBoxLayout *textLayout_;
+	QHBoxLayout *buttonLayout_;
+
+	FlatButton *settingsButton_;
+
+	QLabel *displayNameLabel_;
+	QLabel *userIdLabel_;
+
+	QString display_name_;
+	QString userid_;
+
+	QImage avatar_image_;
+};
+
+#endif
diff --git a/include/WelcomePage.h b/include/WelcomePage.h
new file mode 100644
index 00000000..3cd6e664
--- /dev/null
+++ b/include/WelcomePage.h
@@ -0,0 +1,61 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WELCOMEPAGE_H
+#define WELCOMEPAGE_H
+
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QSpacerItem>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#include "RaisedButton.h"
+
+class WelcomePage : public QWidget
+{
+	Q_OBJECT
+
+public:
+	explicit WelcomePage(QWidget *parent = 0);
+	~WelcomePage();
+
+signals:
+	// Notify that the user wants to login in.
+	void userLogin();
+
+	// Notify that the user wants to register.
+	void userRegister();
+
+private slots:
+	void onLoginButtonClicked();
+	void onRegisterButtonClicked();
+
+private:
+	QVBoxLayout *top_layout_;
+	QHBoxLayout *button_layout_;
+
+	QLabel *intro_banner_;
+	QLabel *intro_text_;
+
+	QSpacerItem *button_spacer_;
+
+	RaisedButton *register_button_;
+	RaisedButton *login_button_;
+};
+
+#endif  // WELCOMEPAGE_H
diff --git a/include/ui/Avatar.h b/include/ui/Avatar.h
new file mode 100644
index 00000000..afbf6aad
--- /dev/null
+++ b/include/ui/Avatar.h
@@ -0,0 +1,51 @@
+#ifndef UI_AVATAR_H
+#define UI_AVATAR_H
+
+#include <QIcon>
+#include <QImage>
+#include <QPixmap>
+#include <QWidget>
+
+#include "Theme.h"
+
+class Avatar : public QWidget
+{
+	Q_OBJECT
+
+	Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+	Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+
+public:
+	explicit Avatar(QWidget *parent = 0);
+	~Avatar();
+
+	void setBackgroundColor(const QColor &color);
+	void setIcon(const QIcon &icon);
+	void setImage(const QImage &image);
+	void setLetter(const QChar &letter);
+	void setSize(int size);
+	void setTextColor(const QColor &color);
+
+	QColor backgroundColor() const;
+	QColor textColor() const;
+	int size() const;
+
+	QSize sizeHint() const override;
+
+protected:
+	void paintEvent(QPaintEvent *event) override;
+
+private:
+	void init();
+
+	ui::AvatarType type_;
+	QChar letter_;
+	QColor background_color_;
+	QColor text_color_;
+	QIcon icon_;
+	QImage image_;
+	QPixmap pixmap_;
+	int size_;
+};
+
+#endif  // UI_AVATAR_H
diff --git a/include/ui/Badge.h b/include/ui/Badge.h
new file mode 100644
index 00000000..774b03ad
--- /dev/null
+++ b/include/ui/Badge.h
@@ -0,0 +1,63 @@
+#ifndef UI_BADGE_H
+#define UI_BADGE_H
+
+#include <QColor>
+#include <QIcon>
+#include <QWidget>
+#include <QtGlobal>
+
+#include "OverlayWidget.h"
+
+class Badge : public OverlayWidget
+{
+	Q_OBJECT
+
+	Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+	Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+	Q_PROPERTY(QPointF relativePosition WRITE setRelativePosition READ relativePosition)
+
+public:
+	explicit Badge(QWidget *parent = 0);
+	explicit Badge(const QIcon &icon, QWidget *parent = 0);
+	explicit Badge(const QString &text, QWidget *parent = 0);
+	~Badge();
+
+	void setBackgroundColor(const QColor &color);
+	void setTextColor(const QColor &color);
+	void setIcon(const QIcon &icon);
+	void setRelativePosition(const QPointF &pos);
+	void setRelativePosition(qreal x, qreal y);
+	void setRelativeXPosition(qreal x);
+	void setRelativeYPosition(qreal y);
+	void setText(const QString &text);
+
+	QIcon icon() const;
+	QString text() const;
+	QColor backgroundColor() const;
+	QColor textColor() const;
+	QPointF relativePosition() const;
+	QSize sizeHint() const override;
+	qreal relativeXPosition() const;
+	qreal relativeYPosition() const;
+
+protected:
+	void paintEvent(QPaintEvent *event) override;
+	int getDiameter() const;
+
+private:
+	void init();
+
+	QColor background_color_;
+	QColor text_color_;
+
+	QIcon icon_;
+	QSize size_;
+	QString text_;
+
+	int padding_;
+
+	qreal x_;
+	qreal y_;
+};
+
+#endif  // UI_BADGE_H
diff --git a/include/ui/FlatButton.h b/include/ui/FlatButton.h
new file mode 100644
index 00000000..047890c7
--- /dev/null
+++ b/include/ui/FlatButton.h
@@ -0,0 +1,210 @@
+#ifndef UI_FLAT_BUTTON_H
+#define UI_FLAT_BUTTON_H
+
+#include <QPaintEvent>
+#include <QPainter>
+#include <QPushButton>
+#include <QSequentialAnimationGroup>
+#include <QStateMachine>
+
+#include "RippleOverlay.h"
+#include "Theme.h"
+
+class FlatButton;
+
+class FlatButtonStateMachine : public QStateMachine
+{
+	Q_OBJECT
+
+	Q_PROPERTY(qreal overlayOpacity WRITE setOverlayOpacity READ overlayOpacity)
+	Q_PROPERTY(qreal checkedOverlayProgress WRITE setCheckedOverlayProgress READ checkedOverlayProgress)
+	Q_PROPERTY(qreal haloOpacity WRITE setHaloOpacity READ haloOpacity)
+	Q_PROPERTY(qreal haloSize WRITE setHaloSize READ haloSize)
+	Q_PROPERTY(qreal haloScaleFactor WRITE setHaloScaleFactor READ haloScaleFactor)
+
+public:
+	explicit FlatButtonStateMachine(FlatButton *parent);
+	~FlatButtonStateMachine();
+
+	void setOverlayOpacity(qreal opacity);
+	void setCheckedOverlayProgress(qreal opacity);
+	void setHaloOpacity(qreal opacity);
+	void setHaloSize(qreal size);
+	void setHaloScaleFactor(qreal factor);
+
+	inline qreal overlayOpacity() const;
+	inline qreal checkedOverlayProgress() const;
+	inline qreal haloOpacity() const;
+	inline qreal haloSize() const;
+	inline qreal haloScaleFactor() const;
+
+	void startAnimations();
+	void setupProperties();
+	void updateCheckedStatus();
+
+signals:
+	void buttonPressed();
+	void buttonChecked();
+	void buttonUnchecked();
+
+protected:
+	bool eventFilter(QObject *watched, QEvent *event) override;
+
+private:
+	void addTransition(QObject *object, const char *signal, QState *fromState, QState *toState);
+	void addTransition(QObject *object, QEvent::Type eventType, QState *fromState, QState *toState);
+	void addTransition(QAbstractTransition *transition, QState *fromState, QState *toState);
+
+	FlatButton *const button_;
+
+	QState *const top_level_state_;
+	QState *const config_state_;
+	QState *const checkable_state_;
+	QState *const checked_state_;
+	QState *const unchecked_state_;
+	QState *const neutral_state_;
+	QState *const neutral_focused_state_;
+	QState *const hovered_state_;
+	QState *const hovered_focused_state_;
+	QState *const pressed_state_;
+
+	QSequentialAnimationGroup *const halo_animation_;
+
+	qreal overlay_opacity_;
+	qreal checked_overlay_progress_;
+	qreal halo_opacity_;
+	qreal halo_size_;
+	qreal halo_scale_factor_;
+
+	bool was_checked_;
+};
+
+inline qreal FlatButtonStateMachine::overlayOpacity() const
+{
+	return overlay_opacity_;
+}
+
+inline qreal FlatButtonStateMachine::checkedOverlayProgress() const
+{
+	return checked_overlay_progress_;
+}
+
+inline qreal FlatButtonStateMachine::haloOpacity() const
+{
+	return halo_opacity_;
+}
+
+inline qreal FlatButtonStateMachine::haloSize() const
+{
+	return halo_size_;
+}
+
+inline qreal FlatButtonStateMachine::haloScaleFactor() const
+{
+	return halo_scale_factor_;
+}
+
+class FlatButton : public QPushButton
+{
+	Q_OBJECT
+
+	Q_PROPERTY(QColor foregroundColor WRITE setForegroundColor READ foregroundColor)
+	Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+	Q_PROPERTY(QColor overlayColor WRITE setOverlayColor READ overlayColor)
+	Q_PROPERTY(QColor disabledForegroundColor WRITE setDisabledForegroundColor READ disabledForegroundColor)
+	Q_PROPERTY(QColor disabledBackgroundColor WRITE setDisabledBackgroundColor READ disabledBackgroundColor)
+	Q_PROPERTY(qreal fontSize WRITE setFontSize READ fontSize)
+
+public:
+	explicit FlatButton(QWidget *parent = 0, ui::ButtonPreset preset = ui::FlatPreset);
+	explicit FlatButton(const QString &text, QWidget *parent = 0, ui::ButtonPreset preset = ui::FlatPreset);
+	FlatButton(const QString &text, ui::Role role, QWidget *parent = 0, ui::ButtonPreset preset = ui::FlatPreset);
+	~FlatButton();
+
+	void applyPreset(ui::ButtonPreset preset);
+
+	void setBackgroundColor(const QColor &color);
+	void setBackgroundMode(Qt::BGMode mode);
+	void setBaseOpacity(qreal opacity);
+	void setCheckable(bool value);
+	void setCornerRadius(qreal radius);
+	void setDisabledBackgroundColor(const QColor &color);
+	void setDisabledForegroundColor(const QColor &color);
+	void setFixedRippleRadius(qreal radius);
+	void setFontSize(qreal size);
+	void setForegroundColor(const QColor &color);
+	void setHaloVisible(bool visible);
+	void setHasFixedRippleRadius(bool value);
+	void setIconPlacement(ui::ButtonIconPlacement placement);
+	void setOverlayColor(const QColor &color);
+	void setOverlayStyle(ui::OverlayStyle style);
+	void setRippleStyle(ui::RippleStyle style);
+	void setRole(ui::Role role);
+
+	QColor foregroundColor() const;
+	QColor backgroundColor() const;
+	QColor overlayColor() const;
+	QColor disabledForegroundColor() const;
+	QColor disabledBackgroundColor() const;
+
+	qreal fontSize() const;
+	qreal cornerRadius() const;
+	qreal baseOpacity() const;
+
+	bool isHaloVisible() const;
+	bool hasFixedRippleRadius() const;
+
+	ui::Role role() const;
+	ui::OverlayStyle overlayStyle() const;
+	ui::RippleStyle rippleStyle() const;
+	ui::ButtonIconPlacement iconPlacement() const;
+
+	Qt::BGMode backgroundMode() const;
+
+	QSize sizeHint() const override;
+
+protected:
+	enum {
+		IconPadding = 12
+	};
+
+	void checkStateSet() override;
+	void mousePressEvent(QMouseEvent *event) override;
+	void mouseReleaseEvent(QMouseEvent *event) override;
+	void resizeEvent(QResizeEvent *event) override;
+	void paintEvent(QPaintEvent *event) override;
+
+	virtual void paintBackground(QPainter *painter);
+	virtual void paintHalo(QPainter *painter);
+	virtual void paintForeground(QPainter *painter);
+	virtual void updateClipPath();
+
+	void init();
+
+private:
+	RippleOverlay *ripple_overlay_;
+	FlatButtonStateMachine *state_machine_;
+
+	ui::Role role_;
+	ui::RippleStyle ripple_style_;
+	ui::ButtonIconPlacement icon_placement_;
+	ui::OverlayStyle overlay_style_;
+
+	Qt::BGMode bg_mode_;
+
+	QColor background_color_;
+	QColor foreground_color_;
+	QColor overlay_color_;
+	QColor disabled_color_;
+	QColor disabled_background_color_;
+
+	qreal fixed_ripple_radius_;
+	qreal corner_radius_;
+	qreal base_opacity_;
+	qreal font_size_;
+
+	bool use_fixed_ripple_radius_;
+	bool halo_visible_;
+};
+
+#endif  // UI_FLAT_BUTTON_H
diff --git a/include/ui/OverlayWidget.h b/include/ui/OverlayWidget.h
new file mode 100644
index 00000000..020393ad
--- /dev/null
+++ b/include/ui/OverlayWidget.h
@@ -0,0 +1,23 @@
+#ifndef UI_OVERLAY_WIDGET_H
+#define UI_OVERLAY_WIDGET_H
+
+#include <QEvent>
+#include <QObject>
+#include <QWidget>
+
+class OverlayWidget : public QWidget
+{
+	Q_OBJECT
+
+public:
+	explicit OverlayWidget(QWidget *parent = 0);
+	~OverlayWidget();
+
+protected:
+	bool event(QEvent *event) override;
+	bool eventFilter(QObject *obj, QEvent *event) override;
+
+	QRect overlayGeometry() const;
+};
+
+#endif  // UI_OVERLAY_WIDGET_H
diff --git a/include/ui/RaisedButton.h b/include/ui/RaisedButton.h
new file mode 100644
index 00000000..7a46173f
--- /dev/null
+++ b/include/ui/RaisedButton.h
@@ -0,0 +1,31 @@
+#ifndef UI_RAISED_BUTTON_H
+#define UI_RAISED_BUTTON_H
+
+#include <QGraphicsDropShadowEffect>
+#include <QState>
+#include <QStateMachine>
+
+#include "FlatButton.h"
+
+class RaisedButton : public FlatButton
+{
+	Q_OBJECT
+
+public:
+	explicit RaisedButton(QWidget *parent = 0);
+	explicit RaisedButton(const QString &text, QWidget *parent = 0);
+	~RaisedButton();
+
+protected:
+	bool event(QEvent *event) override;
+
+private:
+	void init();
+
+	QStateMachine *shadow_state_machine_;
+	QState *normal_state_;
+	QState *pressed_state_;
+	QGraphicsDropShadowEffect *effect_;
+};
+
+#endif  // UI_RAISED_BUTTON_H
diff --git a/include/ui/Ripple.h b/include/ui/Ripple.h
new file mode 100644
index 00000000..a66a583e
--- /dev/null
+++ b/include/ui/Ripple.h
@@ -0,0 +1,136 @@
+#ifndef UI_RIPPLE_H
+#define UI_RIPPLE_H
+
+#include <QBrush>
+#include <QEasingCurve>
+#include <QParallelAnimationGroup>
+#include <QPoint>
+#include <QPropertyAnimation>
+
+class RippleOverlay;
+
+class Ripple : public QParallelAnimationGroup
+{
+	Q_OBJECT
+
+	Q_PROPERTY(qreal radius WRITE setRadius READ radius)
+	Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity)
+
+public:
+	explicit Ripple(const QPoint &center, QObject *parent = 0);
+	Ripple(const QPoint &center, RippleOverlay *overlay, QObject *parent = 0);
+	~Ripple();
+
+	inline void setOverlay(RippleOverlay *overlay);
+
+	void setRadius(qreal radius);
+	void setOpacity(qreal opacity);
+	void setColor(const QColor &color);
+	void setBrush(const QBrush &brush);
+
+	inline qreal radius() const;
+	inline qreal opacity() const;
+	inline QColor color() const;
+	inline QBrush brush() const;
+	inline QPoint center() const;
+
+	inline QPropertyAnimation *radiusAnimation() const;
+	inline QPropertyAnimation *opacityAnimation() const;
+
+	inline void setOpacityStartValue(qreal value);
+	inline void setOpacityEndValue(qreal value);
+	inline void setRadiusStartValue(qreal value);
+	inline void setRadiusEndValue(qreal value);
+	inline void setDuration(int msecs);
+
+protected slots:
+	void destroy();
+
+private:
+	Q_DISABLE_COPY(Ripple)
+
+	QPropertyAnimation *animate(const QByteArray &property,
+				    const QEasingCurve &easing = QEasingCurve::OutQuad,
+				    int duration = 800);
+
+	void init();
+
+	RippleOverlay *overlay_;
+
+	QPropertyAnimation *const radius_anim_;
+	QPropertyAnimation *const opacity_anim_;
+
+	qreal radius_;
+	qreal opacity_;
+
+	QPoint center_;
+	QBrush brush_;
+};
+
+inline void Ripple::setOverlay(RippleOverlay *overlay)
+{
+	overlay_ = overlay;
+}
+
+inline qreal Ripple::radius() const
+{
+	return radius_;
+}
+
+inline qreal Ripple::opacity() const
+{
+	return opacity_;
+}
+
+inline QColor Ripple::color() const
+{
+	return brush_.color();
+}
+
+inline QBrush Ripple::brush() const
+{
+	return brush_;
+}
+
+inline QPoint Ripple::center() const
+{
+	return center_;
+}
+
+inline QPropertyAnimation *Ripple::radiusAnimation() const
+{
+	return radius_anim_;
+}
+
+inline QPropertyAnimation *Ripple::opacityAnimation() const
+{
+	return opacity_anim_;
+}
+
+inline void Ripple::setOpacityStartValue(qreal value)
+{
+	opacity_anim_->setStartValue(value);
+}
+
+inline void Ripple::setOpacityEndValue(qreal value)
+{
+	opacity_anim_->setEndValue(value);
+}
+
+inline void Ripple::setRadiusStartValue(qreal value)
+{
+	radius_anim_->setStartValue(value);
+}
+
+inline void Ripple::setRadiusEndValue(qreal value)
+{
+	radius_anim_->setEndValue(value);
+}
+
+inline void Ripple::setDuration(int msecs)
+{
+	radius_anim_->setDuration(msecs);
+	opacity_anim_->setDuration(msecs);
+}
+
+#endif  // UI_RIPPLE_H
diff --git a/include/ui/RippleOverlay.h b/include/ui/RippleOverlay.h
new file mode 100644
index 00000000..54398efa
--- /dev/null
+++ b/include/ui/RippleOverlay.h
@@ -0,0 +1,58 @@
+#ifndef UI_RIPPLE_OVERLAY_H
+#define UI_RIPPLE_OVERLAY_H
+
+#include <QPainterPath>
+
+#include "OverlayWidget.h"
+
+class Ripple;
+
+class RippleOverlay : public OverlayWidget
+{
+	Q_OBJECT
+
+public:
+	explicit RippleOverlay(QWidget *parent = 0);
+	~RippleOverlay();
+
+	void addRipple(Ripple *ripple);
+	void addRipple(const QPoint &position, qreal radius = 300);
+
+	void removeRipple(Ripple *ripple);
+
+	inline void setClipping(bool enable);
+	inline bool hasClipping() const;
+
+	inline void setClipPath(const QPainterPath &path);
+
+protected:
+	void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
+
+private:
+	Q_DISABLE_COPY(RippleOverlay)
+
+	void paintRipple(QPainter *painter, Ripple *ripple);
+
+	QList<Ripple *> ripples_;
+	QPainterPath clip_path_;
+	bool use_clip_;
+};
+
+inline void RippleOverlay::setClipping(bool enable)
+{
+	use_clip_ = enable;
+	update();
+}
+
+inline bool RippleOverlay::hasClipping() const
+{
+	return use_clip_;
+}
+
+inline void RippleOverlay::setClipPath(const QPainterPath &path)
+{
+	clip_path_ = path;
+	update();
+}
+
+#endif  // UI_RIPPLE_OVERLAY_H
diff --git a/include/ui/TextField.h b/include/ui/TextField.h
new file mode 100644
index 00000000..953c8f29
--- /dev/null
+++ b/include/ui/TextField.h
@@ -0,0 +1,170 @@
+#ifndef UI_TEXT_FIELD_H
+#define UI_TEXT_FIELD_H
+
+#include <QColor>
+#include <QLineEdit>
+#include <QPaintEvent>
+#include <QPropertyAnimation>
+#include <QStateMachine>
+#include <QtGlobal>
+
+class TextField;
+class TextFieldLabel;
+class TextFieldStateMachine;
+
+class TextField : public QLineEdit
+{
+	Q_OBJECT
+
+	Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+	Q_PROPERTY(QColor inkColor WRITE setInkColor READ inkColor)
+	Q_PROPERTY(QColor underlineColor WRITE setUnderlineColor READ underlineColor)
+
+public:
+	explicit TextField(QWidget *parent = 0);
+	~TextField();
+
+	void setInkColor(const QColor &color);
+	void setBackgroundColor(const QColor &color);
+	void setLabel(const QString &label);
+	void setLabelColor(const QColor &color);
+	void setLabelFontSize(qreal size);
+	void setShowLabel(bool value);
+	void setTextColor(const QColor &color);
+	void setUnderlineColor(const QColor &color);
+
+	QColor inkColor() const;
+	QColor labelColor() const;
+	QColor textColor() const;
+	QColor underlineColor() const;
+	QColor backgroundColor() const;
+	QString label() const;
+	bool hasLabel() const;
+	qreal labelFontSize() const;
+
+protected:
+	bool event(QEvent *event) override;
+	void paintEvent(QPaintEvent *event) override;
+
+private:
+	void init();
+
+	QColor ink_color_;
+	QColor background_color_;
+	QColor label_color_;
+	QColor text_color_;
+	QColor underline_color_;
+	QString label_text_;
+	TextFieldLabel *label_;
+	TextFieldStateMachine *state_machine_;
+	bool show_label_;
+	qreal label_font_size_;
+};
+
+class TextFieldLabel : public QWidget
+{
+	Q_OBJECT
+
+	Q_PROPERTY(qreal scale WRITE setScale READ scale)
+	Q_PROPERTY(QPointF offset WRITE setOffset READ offset)
+	Q_PROPERTY(QColor color WRITE setColor READ color)
+
+public:
+	TextFieldLabel(TextField *parent);
+	~TextFieldLabel();
+
+	inline void setColor(const QColor &color);
+	inline void setOffset(const QPointF &pos);
+	inline void setScale(qreal scale);
+
+	inline QColor color() const;
+	inline QPointF offset() const;
+	inline qreal scale() const;
+
+protected:
+	void paintEvent(QPaintEvent *event) override;
+
+private:
+	TextField *const text_field_;
+
+	QColor color_;
+	qreal scale_;
+	qreal x_;
+	qreal y_;
+};
+
+inline void TextFieldLabel::setColor(const QColor &color)
+{
+	color_ = color;
+	update();
+}
+
+inline void TextFieldLabel::setOffset(const QPointF &pos)
+{
+	x_ = pos.x();
+	y_ = pos.y();
+	update();
+}
+
+inline void TextFieldLabel::setScale(qreal scale)
+{
+	scale_ = scale;
+	update();
+}
+
+inline QPointF TextFieldLabel::offset() const
+{
+	return QPointF(x_, y_);
+}
+inline qreal TextFieldLabel::scale() const
+{
+	return scale_;
+}
+inline QColor TextFieldLabel::color() const
+{
+	return color_;
+}
+
+class TextFieldStateMachine : public QStateMachine
+{
+	Q_OBJECT
+
+	Q_PROPERTY(qreal progress WRITE setProgress READ progress)
+
+public:
+	TextFieldStateMachine(TextField *parent);
+	~TextFieldStateMachine();
+
+	inline void setProgress(qreal progress);
+	void setLabel(TextFieldLabel *label);
+
+	inline qreal progress() const;
+
+public slots:
+	void setupProperties();
+
+private:
+	QPropertyAnimation *color_anim_;
+	QPropertyAnimation *offset_anim_;
+
+	QState *focused_state_;
+	QState *normal_state_;
+
+	TextField *text_field_;
+	TextFieldLabel *label_;
+
+	qreal progress_;
+};
+
+inline void TextFieldStateMachine::setProgress(qreal progress)
+{
+	progress_ = progress;
+	text_field_->update();
+}
+
+inline qreal TextFieldStateMachine::progress() const
+{
+	return progress_;
+}
+
+#endif  // UI_TEXT_FIELD_H
diff --git a/include/ui/Theme.h b/include/ui/Theme.h
new file mode 100644
index 00000000..41739a98
--- /dev/null
+++ b/include/ui/Theme.h
@@ -0,0 +1,89 @@
+#ifndef UI_THEME_H
+#define UI_THEME_H
+
+#include <QColor>
+#include <QHash>
+#include <QObject>
+
+namespace ui
+{
+enum AvatarType {
+	Icon,
+	Image,
+	Letter
+};
+
+// Default font size.
+const int FontSize = 16;
+
+// Default avatar size. Width and height.
+const int AvatarSize = 40;
+
+enum ButtonPreset {
+	FlatPreset,
+	CheckablePreset
+};
+
+enum RippleStyle {
+	CenteredRipple,
+	PositionedRipple,
+	NoRipple
+};
+
+enum OverlayStyle {
+	NoOverlay,
+	TintedOverlay,
+	GrayOverlay
+};
+
+enum Role {
+	Default,
+	Primary,
+	Secondary
+};
+
+enum ButtonIconPlacement {
+	LeftIcon,
+	RightIcon
+};
+
+enum ProgressType {
+	DeterminateProgress,
+	IndeterminateProgress
+};
+
+enum Color {
+	Black,
+	BrightWhite,
+	FadedWhite,
+	MediumWhite,
+	DarkGreen,
+	LightGreen,
+	BrightGreen,
+	Gray,
+	Red,
+	Blue,
+	Transparent
+};
+
+}  // namespace ui
+
+class Theme : public QObject
+{
+	Q_OBJECT
+public:
+	explicit Theme(QObject *parent = 0);
+	~Theme();
+
+	QColor getColor(const QString &key) const;
+
+	void setColor(const QString &key, const QColor &color);
+	void setColor(const QString &key, ui::Color &color);
+
+private:
+	QColor rgba(int r, int g, int b, qreal a) const;
+
+	QHash<QString, QColor> colors_;
+};
+
+#endif  // UI_THEME_H
diff --git a/include/ui/ThemeManager.h b/include/ui/ThemeManager.h
new file mode 100644
index 00000000..426d71ec
--- /dev/null
+++ b/include/ui/ThemeManager.h
@@ -0,0 +1,33 @@
+#ifndef UI_THEME_MANAGER_H
+#define UI_THEME_MANAGER_H
+
+#include <QCommonStyle>
+
+#include "Theme.h"
+
+class ThemeManager : public QCommonStyle
+{
+	Q_OBJECT
+
+public:
+	inline static ThemeManager &instance();
+
+	void setTheme(Theme *theme);
+	QColor themeColor(const QString &key) const;
+
+private:
+	ThemeManager();
+
+	ThemeManager(ThemeManager const &);
+	void operator=(ThemeManager const &);
+
+	Theme *theme_;
+};
+
+inline ThemeManager &ThemeManager::instance()
+{
+	static ThemeManager instance;
+	return instance;
+}
+
+#endif  // UI_THEME_MANAGER_H
diff --git a/resources/fonts/LICENSE.txt b/resources/fonts/LICENSE.txt
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/resources/fonts/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/resources/fonts/OpenSans-Bold.ttf b/resources/fonts/OpenSans-Bold.ttf
new file mode 100644
index 00000000..fd79d43b
--- /dev/null
+++ b/resources/fonts/OpenSans-Bold.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-BoldItalic.ttf b/resources/fonts/OpenSans-BoldItalic.ttf
new file mode 100644
index 00000000..9bc80095
--- /dev/null
+++ b/resources/fonts/OpenSans-BoldItalic.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-ExtraBold.ttf b/resources/fonts/OpenSans-ExtraBold.ttf
new file mode 100644
index 00000000..21f6f84a
--- /dev/null
+++ b/resources/fonts/OpenSans-ExtraBold.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-ExtraBoldItalic.ttf b/resources/fonts/OpenSans-ExtraBoldItalic.ttf
new file mode 100644
index 00000000..31cb6883
--- /dev/null
+++ b/resources/fonts/OpenSans-ExtraBoldItalic.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-Italic.ttf b/resources/fonts/OpenSans-Italic.ttf
new file mode 100644
index 00000000..c90da48f
--- /dev/null
+++ b/resources/fonts/OpenSans-Italic.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-Light.ttf b/resources/fonts/OpenSans-Light.ttf
new file mode 100644
index 00000000..0d381897
--- /dev/null
+++ b/resources/fonts/OpenSans-Light.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-LightItalic.ttf b/resources/fonts/OpenSans-LightItalic.ttf
new file mode 100644
index 00000000..68299c4b
--- /dev/null
+++ b/resources/fonts/OpenSans-LightItalic.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-Regular.ttf b/resources/fonts/OpenSans-Regular.ttf
new file mode 100644
index 00000000..db433349
--- /dev/null
+++ b/resources/fonts/OpenSans-Regular.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-Semibold.ttf b/resources/fonts/OpenSans-Semibold.ttf
new file mode 100644
index 00000000..1a7679e3
--- /dev/null
+++ b/resources/fonts/OpenSans-Semibold.ttf
Binary files differdiff --git a/resources/fonts/OpenSans-SemiboldItalic.ttf b/resources/fonts/OpenSans-SemiboldItalic.ttf
new file mode 100644
index 00000000..59b6d16b
--- /dev/null
+++ b/resources/fonts/OpenSans-SemiboldItalic.ttf
Binary files differdiff --git a/resources/icons/add-file.png b/resources/icons/add-file.png
new file mode 100644
index 00000000..806b166d
--- /dev/null
+++ b/resources/icons/add-file.png
Binary files differdiff --git a/resources/icons/clip-dark.png b/resources/icons/clip-dark.png
new file mode 100644
index 00000000..c3c34fac
--- /dev/null
+++ b/resources/icons/clip-dark.png
Binary files differdiff --git a/resources/icons/cog.png b/resources/icons/cog.png
new file mode 100644
index 00000000..84311166
--- /dev/null
+++ b/resources/icons/cog.png
Binary files differdiff --git a/resources/icons/left-angle.png b/resources/icons/left-angle.png
new file mode 100644
index 00000000..71543286
--- /dev/null
+++ b/resources/icons/left-angle.png
Binary files differdiff --git a/resources/icons/left-chevron.png b/resources/icons/left-chevron.png
new file mode 100644
index 00000000..386395fb
--- /dev/null
+++ b/resources/icons/left-chevron.png
Binary files differdiff --git a/resources/icons/plus-symbol.png b/resources/icons/plus-symbol.png
new file mode 100644
index 00000000..35aef7d0
--- /dev/null
+++ b/resources/icons/plus-symbol.png
Binary files differdiff --git a/resources/icons/search.png b/resources/icons/search.png
new file mode 100644
index 00000000..94e2f4fc
--- /dev/null
+++ b/resources/icons/search.png
Binary files differdiff --git a/resources/icons/send-button.png b/resources/icons/send-button.png
new file mode 100644
index 00000000..d19bf69f
--- /dev/null
+++ b/resources/icons/send-button.png
Binary files differdiff --git a/resources/icons/share-dark.png b/resources/icons/share-dark.png
new file mode 100644
index 00000000..3e4d42a8
--- /dev/null
+++ b/resources/icons/share-dark.png
Binary files differdiff --git a/resources/icons/user-shape.png b/resources/icons/user-shape.png
new file mode 100644
index 00000000..1b4b46cc
--- /dev/null
+++ b/resources/icons/user-shape.png
Binary files differdiff --git a/resources/res.qrc b/resources/res.qrc
new file mode 100644
index 00000000..a6e4580a
--- /dev/null
+++ b/resources/res.qrc
@@ -0,0 +1,27 @@
+<RCC>
+    <qresource prefix="/icons">
+        <file>icons/left-chevron.png</file>
+        <file>icons/left-angle.png</file>
+        <file>icons/add-file.png</file>
+        <file>icons/send-button.png</file>
+        <file>icons/cog.png</file>
+        <file>icons/search.png</file>
+        <file>icons/plus-symbol.png</file>
+        <file>icons/clip-dark.png</file>
+        <file>icons/share-dark.png</file>
+        <file>icons/user-shape.png</file>
+    </qresource>
+
+    <qresource prefix="/fonts">
+        <file>fonts/OpenSans-Light.ttf</file>
+        <file>fonts/OpenSans-LightItalic.ttf</file>
+        <file>fonts/OpenSans-Regular.ttf</file>
+        <file>fonts/OpenSans-Italic.ttf</file>
+        <file>fonts/OpenSans-Bold.ttf</file>
+        <file>fonts/OpenSans-BoldItalic.ttf</file>
+        <file>fonts/OpenSans-Semibold.ttf</file>
+        <file>fonts/OpenSans-SemiboldItalic.ttf</file>
+        <file>fonts/OpenSans-ExtraBold.ttf</file>
+        <file>fonts/OpenSans-ExtraBoldItalic.ttf</file>
+    </qresource>
+</RCC>
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
new file mode 100644
index 00000000..a17e8c65
--- /dev/null
+++ b/src/ChatPage.cc
@@ -0,0 +1,277 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ui_ChatPage.h"
+
+#include <QDebug>
+#include <QLabel>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QSettings>
+#include <QWidget>
+
+#include "ChatPage.h"
+#include "Sync.h"
+#include "UserInfoWidget.h"
+
+ChatPage::ChatPage(QWidget *parent)
+    : QWidget(parent)
+    , ui(new Ui::ChatPage)
+    , sync_interval_(2000)
+{
+	ui->setupUi(this);
+	matrix_client_ = new MatrixClient("matrix.org", parent);
+	content_downloader_ = new QNetworkAccessManager(parent);
+
+	room_list_ = new RoomList(this);
+
+	top_bar_ = new TopRoomBar(this);
+	ui->topBarLayout->addWidget(top_bar_);
+
+	view_manager_ = new HistoryViewManager(this);
+	ui->mainContentLayout->addWidget(view_manager_);
+
+	text_input_ = new TextInputWidget(this);
+	ui->contentLayout->addWidget(text_input_);
+
+	user_info_widget_ = new UserInfoWidget(ui->sideBarTopWidget);
+
+	connect(room_list_,
+		SIGNAL(roomChanged(const RoomInfo &)),
+		this,
+		SLOT(changeTopRoomInfo(const RoomInfo &)));
+
+	connect(room_list_,
+		SIGNAL(roomChanged(const RoomInfo &)),
+		view_manager_,
+		SLOT(setHistoryView(const RoomInfo &)));
+
+	connect(room_list_,
+		SIGNAL(fetchRoomAvatar(const QString &, const QUrl &)),
+		this,
+		SLOT(fetchRoomAvatar(const QString &, const QUrl &)));
+
+	connect(text_input_,
+		SIGNAL(sendTextMessage(const QString &)),
+		this,
+		SLOT(sendTextMessage(const QString &)));
+
+	ui->sideBarTopUserInfoLayout->addWidget(user_info_widget_);
+	ui->sideBarMainLayout->addWidget(room_list_);
+
+	connect(matrix_client_,
+		SIGNAL(initialSyncCompleted(SyncResponse)),
+		this,
+		SLOT(initialSyncCompleted(SyncResponse)));
+	connect(matrix_client_,
+		SIGNAL(syncCompleted(SyncResponse)),
+		this,
+		SLOT(syncCompleted(SyncResponse)));
+	connect(matrix_client_,
+		SIGNAL(getOwnProfileResponse(QUrl, QString)),
+		this,
+		SLOT(updateOwnProfileInfo(QUrl, QString)));
+	connect(matrix_client_,
+		SIGNAL(messageSent(QString, int)),
+		this,
+		SLOT(messageSent(QString, int)));
+}
+
+void ChatPage::messageSent(QString event_id, int txn_id)
+{
+	Q_UNUSED(event_id);
+
+	QSettings settings;
+	settings.setValue("client/transaction_id", txn_id + 1);
+}
+
+void ChatPage::sendTextMessage(const QString &msg)
+{
+	auto room = current_room_;
+	matrix_client_->sendTextMessage(current_room_.id(), msg);
+}
+
+void ChatPage::bootstrap(QString userid, QString homeserver, QString token)
+{
+	Q_UNUSED(userid);
+
+	matrix_client_->setServer(homeserver);
+	matrix_client_->setAccessToken(token);
+
+	matrix_client_->getOwnProfile();
+	matrix_client_->initialSync();
+}
+
+void ChatPage::startSync()
+{
+	matrix_client_->sync();
+}
+
+void ChatPage::setOwnAvatar(QByteArray img)
+{
+	if (img.size() == 0)
+		return;
+
+	QPixmap pixmap;
+	pixmap.loadFromData(img);
+	user_info_widget_->setAvatar(pixmap.toImage());
+}
+
+void ChatPage::syncCompleted(SyncResponse response)
+{
+	matrix_client_->setNextBatchToken(response.nextBatch());
+
+	/* room_list_->sync(response.rooms()); */
+	view_manager_->sync(response.rooms());
+}
+
+void ChatPage::initialSyncCompleted(SyncResponse response)
+{
+	if (!response.nextBatch().isEmpty())
+		matrix_client_->setNextBatchToken(response.nextBatch());
+
+	view_manager_->initialize(response.rooms());
+	room_list_->setInitialRooms(response.rooms());
+
+	sync_timer_ = new QTimer(this);
+	connect(sync_timer_, SIGNAL(timeout()), this, SLOT(startSync()));
+	sync_timer_->start(sync_interval_);
+}
+
+// TODO: This function should be part of the matrix client for generic media retrieval.
+void ChatPage::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url)
+{
+	// TODO: move this into a Utils function
+	QList<QString> url_parts = avatar_url.toString().split("mxc://");
+
+	if (url_parts.size() != 2) {
+		qDebug() << "Invalid format for room avatar " << avatar_url.toString();
+		return;
+	}
+
+	QString media_params = url_parts[1];
+	QString media_url = QString("%1/_matrix/media/r0/download/%2")
+				    .arg(matrix_client_->getHomeServer(), media_params);
+
+	QNetworkRequest avatar_request(media_url);
+	QNetworkReply *reply = content_downloader_->get(avatar_request);
+	reply->setProperty("media_params", media_params);
+
+	connect(reply, &QNetworkReply::finished, [this, media_params, roomid, reply]() {
+		reply->deleteLater();
+
+		auto media = reply->property("media_params").toString();
+
+		if (media != media_params)
+			return;
+
+		int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+		if (status == 0) {
+			qDebug() << reply->errorString();
+			return;
+		}
+
+		if (status >= 400) {
+			qWarning() << "Request " << reply->request().url() << " returned " << status;
+			return;
+		}
+
+		auto img = reply->readAll();
+
+		if (img.size() == 0)
+			return;
+
+		QPixmap pixmap;
+		pixmap.loadFromData(img);
+		room_avatars_.insert(roomid, pixmap);
+
+		this->room_list_->updateRoomAvatar(roomid, pixmap.toImage());
+
+		if (current_room_.id() == roomid) {
+			QIcon icon(pixmap);
+			this->top_bar_->updateRoomAvatar(icon);
+		}
+	});
+}
+
+void ChatPage::updateOwnProfileInfo(QUrl avatar_url, QString display_name)
+{
+	QSettings settings;
+	auto userid = settings.value("auth/user_id").toString();
+
+	user_info_widget_->setUserId(userid);
+	user_info_widget_->setDisplayName(display_name);
+
+	// TODO: move this into a Utils function
+	QList<QString> url_parts = avatar_url.toString().split("mxc://");
+
+	if (url_parts.size() != 2) {
+		qDebug() << "Invalid format for media " << avatar_url.toString();
+		return;
+	}
+
+	QString media_params = url_parts[1];
+	QString media_url = QString("%1/_matrix/media/r0/download/%2")
+				    .arg(matrix_client_->getHomeServer(), media_params);
+
+	QNetworkRequest avatar_request(media_url);
+	QNetworkReply *reply = content_downloader_->get(avatar_request);
+	reply->setProperty("media_params", media_params);
+
+	connect(reply, &QNetworkReply::finished, [this, media_params, reply]() {
+		reply->deleteLater();
+
+		auto media = reply->property("media_params").toString();
+
+		if (media != media_params)
+			return;
+
+		int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+		if (status == 0) {
+			qDebug() << reply->errorString();
+			return;
+		}
+
+		if (status >= 400) {
+			qWarning() << "Request " << reply->request().url() << " returned " << status;
+			return;
+		}
+
+		setOwnAvatar(reply->readAll());
+	});
+}
+
+void ChatPage::changeTopRoomInfo(const RoomInfo &info)
+{
+	top_bar_->updateRoomName(info.name());
+	top_bar_->updateRoomTopic(info.topic());
+
+	if (room_avatars_.contains(info.id())) {
+		QIcon icon(room_avatars_.value(info.id()));
+		top_bar_->updateRoomAvatar(icon);
+	}
+
+	current_room_ = info;
+}
+
+ChatPage::~ChatPage()
+{
+	sync_timer_->stop();
+	delete ui;
+}
diff --git a/src/Deserializable.cc b/src/Deserializable.cc
new file mode 100644
index 00000000..967f5ef4
--- /dev/null
+++ b/src/Deserializable.cc
@@ -0,0 +1,31 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonValue>
+
+#include "Deserializable.h"
+
+DeserializationException::DeserializationException(const std::string &msg) : msg_(msg)
+{
+}
+
+const char *DeserializationException::what() const throw()
+{
+	return msg_.c_str();
+}
diff --git a/src/HistoryView.cc b/src/HistoryView.cc
new file mode 100644
index 00000000..0949d17c
--- /dev/null
+++ b/src/HistoryView.cc
@@ -0,0 +1,145 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <random>
+
+#include <QDebug>
+#include <QScrollBar>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QSpacerItem>
+
+#include "HistoryView.h"
+#include "HistoryViewItem.h"
+
+const QList<QString> HistoryView::COLORS({"#FFF46E",
+					  "#A58BFF",
+					  "#50C9BA",
+					  "#9EE6CF",
+					  "#FFDD67",
+					  "#2980B9",
+					  "#FC993C",
+					  "#2772DB",
+					  "#CB8589",
+					  "#DDE8B9",
+					  "#55A44E",
+					  "#A9EEE6",
+					  "#53B759",
+					  "#9E3997",
+					  "#5D89D5",
+					  "#BB86B7",
+					  "#50a0cf",
+					  "#3C989F",
+					  "#5A4592",
+					  "#235e5b",
+					  "#d58247",
+					  "#e0a729",
+					  "#a2b636",
+					  "#4BBE2E"});
+
+HistoryView::HistoryView(QList<Event> events, QWidget *parent)
+    : QWidget(parent)
+{
+	init();
+	addEvents(events);
+}
+
+HistoryView::HistoryView(QWidget *parent)
+    : QWidget(parent)
+{
+	init();
+}
+
+void HistoryView::sliderRangeChanged(int min, int max)
+{
+	Q_UNUSED(min);
+	scroll_area_->verticalScrollBar()->setValue(max);
+}
+
+QString HistoryView::chooseRandomColor()
+{
+	std::random_device random_device;
+	std::mt19937 engine{random_device()};
+	std::uniform_int_distribution<int> dist(0, HistoryView::COLORS.size() - 1);
+
+	return HistoryView::COLORS[dist(engine)];
+}
+
+void HistoryView::addEvents(const QList<Event> &events)
+{
+	for (int i = 0; i < events.size(); i++) {
+		auto event = events[i];
+
+		if (event.type() == "m.room.message") {
+			auto msg_type = event.content().value("msgtype").toString();
+
+			if (msg_type == "m.text" || msg_type == "m.notice") {
+				auto with_sender = last_sender_ != event.sender();
+				auto color = nick_colors_.value(event.sender());
+
+				if (color.isEmpty()) {
+					color = chooseRandomColor();
+					nick_colors_.insert(event.sender(), color);
+				}
+
+				addHistoryItem(event, color, with_sender);
+				last_sender_ = event.sender();
+			} else {
+				qDebug() << "Ignoring message" << msg_type;
+			}
+		}
+	}
+}
+
+void HistoryView::init()
+{
+	top_layout_ = new QVBoxLayout(this);
+	top_layout_->setSpacing(0);
+	top_layout_->setMargin(0);
+
+	scroll_area_ = new QScrollArea(this);
+	scroll_area_->setWidgetResizable(true);
+
+	scroll_widget_ = new QWidget();
+
+	scroll_layout_ = new QVBoxLayout();
+	scroll_layout_->addStretch(1);
+	scroll_layout_->setSpacing(0);
+
+	scroll_widget_->setLayout(scroll_layout_);
+
+	scroll_area_->setWidget(scroll_widget_);
+
+	top_layout_->addWidget(scroll_area_);
+
+	setLayout(top_layout_);
+
+	connect(scroll_area_->verticalScrollBar(),
+		SIGNAL(rangeChanged(int, int)),
+		this,
+		SLOT(sliderRangeChanged(int, int)));
+}
+
+void HistoryView::addHistoryItem(Event event, QString color, bool with_sender)
+{
+	// TODO: Probably create another function instead of passing the flag.
+	HistoryViewItem *item = new HistoryViewItem(event, with_sender, color, scroll_widget_);
+	scroll_layout_->addWidget(item);
+}
+
+HistoryView::~HistoryView()
+{
+}
diff --git a/src/HistoryViewItem.cc b/src/HistoryViewItem.cc
new file mode 100644
index 00000000..04c42f45
--- /dev/null
+++ b/src/HistoryViewItem.cc
@@ -0,0 +1,80 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDateTime>
+#include <QDebug>
+
+#include "HistoryViewItem.h"
+
+HistoryViewItem::HistoryViewItem(Event event, bool with_sender, QString color, QWidget *parent)
+    : QWidget(parent)
+{
+	QString sender = "";
+
+	if (with_sender)
+		sender = event.sender().split(":")[0].split("@")[1];
+
+	auto body = event.content().value("body").toString();
+
+	auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
+	auto local_time = timestamp.toString("HH:mm");
+
+	time_label_ = new QLabel(this);
+	time_label_->setWordWrap(true);
+	QString msg(
+		"<html>"
+		"<head/>"
+		"<body>"
+		"   <span style=\"font-size: 7pt; color: #5d6565;\">"
+		"   %1"
+		"   </span>"
+		"</body>"
+		"</html>");
+	time_label_->setText(msg.arg(local_time));
+	time_label_->setStyleSheet("margin-left: 7px; margin-right: 7px; margin-top: 0;");
+	time_label_->setAlignment(Qt::AlignTop);
+
+	content_label_ = new QLabel(this);
+	content_label_->setWordWrap(true);
+	content_label_->setAlignment(Qt::AlignTop);
+	content_label_->setStyleSheet("margin: 0;");
+	QString content(
+		"<html>"
+		"<head/>"
+		"<body>"
+		"   <span style=\"font-size: 10pt; font-weight: 600; color: %1\">"
+		"   %2"
+		"   </span>"
+		"   <span style=\"font-size: 10pt;\">"
+		"   %3"
+		"   </span>"
+		"</body>"
+		"</html>");
+	content_label_->setText(content.arg(color).arg(sender).arg(body));
+
+	top_layout_ = new QHBoxLayout();
+	top_layout_->setMargin(0);
+
+	top_layout_->addWidget(time_label_);
+	top_layout_->addWidget(content_label_, 1);
+
+	setLayout(top_layout_);
+}
+
+HistoryViewItem::~HistoryViewItem()
+{
+}
diff --git a/src/HistoryViewManager.cc b/src/HistoryViewManager.cc
new file mode 100644
index 00000000..c7292747
--- /dev/null
+++ b/src/HistoryViewManager.cc
@@ -0,0 +1,83 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+#include <QStackedWidget>
+#include <QWidget>
+
+#include "HistoryView.h"
+#include "HistoryViewManager.h"
+
+HistoryViewManager::HistoryViewManager(QWidget *parent)
+    : QStackedWidget(parent)
+{
+	setStyleSheet(
+		"QWidget {background: #171919; color: #ebebeb;}"
+		"QScrollBar:vertical { background-color: #171919; width: 10px; border-radius: 20px; margin: 0px 2px 0 2px; }"
+		"QScrollBar::handle:vertical { border-radius : 50px; background-color : #1c3133; }"
+		"QScrollBar::add-line:vertical { border: none; background: none; }"
+		"QScrollBar::sub-line:vertical { border: none; background: none; }");
+}
+
+HistoryViewManager::~HistoryViewManager()
+{
+}
+
+void HistoryViewManager::initialize(const Rooms &rooms)
+{
+	for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
+		auto roomid = it.key();
+		auto events = it.value().timeline().events();
+
+		// Create a history view with the room events.
+		HistoryView *view = new HistoryView(events);
+		views_.insert(it.key(), view);
+
+		// Add the view in the widget stack.
+		addWidget(view);
+	}
+}
+
+void HistoryViewManager::sync(const Rooms &rooms)
+{
+	for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
+		auto roomid = it.key();
+
+		if (!views_.contains(roomid)) {
+			qDebug() << "Ignoring event from unknown room";
+			continue;
+		}
+
+		auto view = views_.value(roomid);
+		auto events = it.value().timeline().events();
+
+		view->addEvents(events);
+	}
+}
+
+void HistoryViewManager::setHistoryView(const RoomInfo &info)
+{
+	if (!views_.contains(info.id())) {
+		qDebug() << "Room List id is not present in view manager";
+		qDebug() << info.name();
+		return;
+	}
+
+	auto widget = views_.value(info.id());
+
+	setCurrentWidget(widget);
+}
diff --git a/src/InputValidator.cc b/src/InputValidator.cc
new file mode 100644
index 00000000..3713c501
--- /dev/null
+++ b/src/InputValidator.cc
@@ -0,0 +1,31 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "InputValidator.h"
+
+// FIXME: Maybe change the regex to match the real Matrix ID format and not email.
+InputValidator::InputValidator(QObject *parent)
+    : matrix_id_("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}")
+    , matrix_localpart_("[A-za-z0-9._%+-]{3,}")
+    , matrix_password_(".{8,}")
+    , server_domain_("(?!\\-)(?:[a-zA-Z\\d\\-]{0,62}[a-zA-Z\\d]\\.){1,126}(?!\\d+)[a-zA-Z\\d]{1,63}")
+{
+	id_ = new QRegExpValidator(matrix_id_, parent);
+	localpart_ = new QRegExpValidator(matrix_localpart_, parent);
+	password_ = new QRegExpValidator(matrix_password_, parent);
+	domain_ = new QRegExpValidator(server_domain_, parent);
+}
diff --git a/src/Login.cc b/src/Login.cc
new file mode 100644
index 00000000..f3b8e2f4
--- /dev/null
+++ b/src/Login.cc
@@ -0,0 +1,89 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonValue>
+
+#include "Deserializable.h"
+#include "Login.h"
+
+LoginRequest::LoginRequest()
+{
+}
+
+LoginRequest::LoginRequest(QString username, QString password)
+    : user_(username)
+    , password_(password)
+{
+}
+
+QByteArray LoginRequest::serialize()
+{
+	QJsonObject body{
+		{"type", "m.login.password"},
+		{"user", user_},
+		{"password", password_}};
+
+	return QJsonDocument(body).toJson(QJsonDocument::Compact);
+}
+
+void LoginRequest::setPassword(QString password)
+{
+	password_ = password;
+}
+
+void LoginRequest::setUser(QString username)
+{
+	user_ = username;
+}
+
+QString LoginResponse::getAccessToken()
+{
+	return access_token_;
+}
+
+QString LoginResponse::getHomeServer()
+{
+	return home_server_;
+}
+
+QString LoginResponse::getUserId()
+{
+	return user_id_;
+}
+
+void LoginResponse::deserialize(QJsonDocument data) throw(DeserializationException)
+{
+	if (!data.isObject())
+		throw DeserializationException("Login response is not a JSON object");
+
+	QJsonObject object = data.object();
+
+	if (object.value("access_token") == QJsonValue::Undefined)
+		throw DeserializationException("Login: missing access_token param");
+
+	if (object.value("home_server") == QJsonValue::Undefined)
+		throw DeserializationException("Login: missing home_server param");
+
+	if (object.value("user_id") == QJsonValue::Undefined)
+		throw DeserializationException("Login: missing user_id param");
+
+	access_token_ = object.value("access_token").toString();
+	home_server_ = object.value("home_server").toString();
+	user_id_ = object.value("user_id").toString();
+}
diff --git a/src/LoginPage.cc b/src/LoginPage.cc
new file mode 100644
index 00000000..68927c33
--- /dev/null
+++ b/src/LoginPage.cc
@@ -0,0 +1,147 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+
+#include "LoginPage.h"
+
+LoginPage::LoginPage(QWidget *parent)
+    : QWidget(parent)
+    , matrix_id_validator_(new InputValidator(parent))
+{
+	top_layout_ = new QVBoxLayout();
+
+	back_layout_ = new QHBoxLayout();
+	back_layout_->setSpacing(0);
+	back_layout_->setContentsMargins(5, 5, -1, -1);
+
+	back_button_ = new FlatButton(this);
+	back_button_->setMinimumSize(QSize(30, 30));
+	back_button_->setCursor(QCursor(Qt::PointingHandCursor));
+
+	QIcon icon;
+	icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off);
+
+	back_button_->setIcon(icon);
+	back_button_->setIconSize(QSize(24, 24));
+
+	back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
+	back_layout_->addStretch(1);
+
+	logo_layout_ = new QHBoxLayout();
+	logo_layout_->setContentsMargins(0, 20, 0, 20);
+	logo_ = new QLabel(this);
+	logo_->setText("nheko");
+	logo_->setStyleSheet("font-size: 22pt; font-weight: 400;");
+	logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
+
+	form_wrapper_ = new QHBoxLayout();
+	form_widget_ = new QWidget();
+	form_widget_->setMinimumSize(QSize(350, 200));
+
+	form_layout_ = new QVBoxLayout();
+	form_layout_->setSpacing(20);
+	form_layout_->setContentsMargins(0, 00, 0, 30);
+	form_widget_->setLayout(form_layout_);
+
+	form_wrapper_->addStretch(1);
+	form_wrapper_->addWidget(form_widget_);
+	form_wrapper_->addStretch(1);
+
+	username_input_ = new TextField();
+	username_input_->setLabel("Username");
+	username_input_->setInkColor("#577275");
+	username_input_->setBackgroundColor("#f9f9f9");
+
+	password_input_ = new TextField();
+	password_input_->setLabel("Password");
+	password_input_->setInkColor("#577275");
+	password_input_->setBackgroundColor("#f9f9f9");
+	password_input_->setEchoMode(QLineEdit::Password);
+
+	form_layout_->addWidget(username_input_, Qt::AlignHCenter, 0);
+	form_layout_->addWidget(password_input_, Qt::AlignHCenter, 0);
+
+	button_layout_ = new QHBoxLayout();
+	button_layout_->setSpacing(0);
+	button_layout_->setContentsMargins(0, 0, 0, 50);
+
+	login_button_ = new RaisedButton("LOGIN", this);
+	login_button_->setBackgroundColor(QColor("#171919"));
+	login_button_->setForegroundColor(QColor("#ebebeb"));
+	login_button_->setMinimumSize(350, 65);
+	login_button_->setCursor(QCursor(Qt::PointingHandCursor));
+	login_button_->setFontSize(17);
+	login_button_->setCornerRadius(3);
+
+	button_layout_->addStretch(1);
+	button_layout_->addWidget(login_button_);
+	button_layout_->addStretch(1);
+
+	error_label_ = new QLabel(this);
+	error_label_->setStyleSheet("color: #E22826; font-size: 11pt;");
+
+	top_layout_->addLayout(back_layout_);
+	top_layout_->addStretch(1);
+	top_layout_->addLayout(logo_layout_);
+	top_layout_->addLayout(form_wrapper_);
+	top_layout_->addStretch(1);
+	top_layout_->addLayout(button_layout_);
+	top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
+	top_layout_->addStretch(1);
+
+	connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
+	connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked()));
+	connect(username_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
+	connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
+
+	username_input_->setValidator(matrix_id_validator_->id_);
+
+	setLayout(top_layout_);
+}
+
+void LoginPage::loginError(QString error)
+{
+	qWarning() << "Error Message: " << error;
+	error_label_->setText(error);
+}
+
+void LoginPage::onLoginButtonClicked()
+{
+	error_label_->setText("");
+
+	if (!username_input_->hasAcceptableInput()) {
+		loginError("Invalid Matrix ID");
+	} else if (password_input_->text().isEmpty()) {
+		loginError("Empty password");
+	} else {
+		QString user = username_input_->text().split("@").at(0);
+		QString home_server = username_input_->text().split("@").at(1);
+		QString password = password_input_->text();
+
+		emit userLogin(user, password, home_server);
+	}
+}
+
+void LoginPage::onBackButtonClicked()
+{
+	emit backButtonClicked();
+}
+
+LoginPage::~LoginPage()
+{
+}
diff --git a/src/MainWindow.cc b/src/MainWindow.cc
new file mode 100644
index 00000000..82976f23
--- /dev/null
+++ b/src/MainWindow.cc
@@ -0,0 +1,134 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "MainWindow.h"
+#include "ui_MainWindow.h"
+
+#include <QLayout>
+#include <QNetworkReply>
+#include <QSettings>
+
+MainWindow::MainWindow(QWidget *parent)
+    : QMainWindow(parent)
+    , ui_(new Ui::MainWindow)
+    , welcome_page_(new WelcomePage(parent))
+    , login_page_(new LoginPage(parent))
+    , register_page_(new RegisterPage(parent))
+    , chat_page_(new ChatPage(parent))
+    , matrix_client_(new MatrixClient("matrix.org", parent))
+{
+	ui_->setupUi(this);
+
+	// Initialize sliding widget manager.
+	sliding_stack_ = new SlidingStackWidget(this);
+	sliding_stack_->addWidget(welcome_page_);
+	sliding_stack_->addWidget(login_page_);
+	sliding_stack_->addWidget(register_page_);
+	sliding_stack_->addWidget(chat_page_);
+
+	setCentralWidget(sliding_stack_);
+
+	connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
+	connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
+
+	connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
+	connect(login_page_,
+		SIGNAL(userLogin(const QString &, const QString &, const QString &)),
+		this,
+		SLOT(matrixLogin(const QString &, const QString &, const QString &)));
+
+	connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
+	connect(register_page_,
+		SIGNAL(registerUser(const QString &, const QString &, const QString &)),
+		this,
+		SLOT(matrixRegister(const QString &, const QString &, const QString &)));
+
+	connect(matrix_client_, SIGNAL(loginError(QString)), login_page_, SLOT(loginError(QString)));
+	connect(matrix_client_,
+		SIGNAL(loginSuccess(QString, QString, QString)),
+		this,
+		SLOT(showChatPage(QString, QString, QString)));
+}
+
+void MainWindow::matrixLogin(const QString &username, const QString &password, const QString &home_server)
+{
+	qDebug() << "About to login into Matrix";
+	qDebug() << "Userame: " << username;
+
+	matrix_client_->setServer(home_server);
+	matrix_client_->login(username, password);
+}
+
+void MainWindow::showChatPage(QString userid, QString homeserver, QString token)
+{
+	QSettings settings;
+	settings.setValue("auth/access_token", token);
+	settings.setValue("auth/home_server", homeserver);
+	settings.setValue("auth/user_id", userid);
+
+	int index = sliding_stack_->getWidgetIndex(chat_page_);
+	sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
+
+	chat_page_->bootstrap(userid, homeserver, token);
+}
+
+void MainWindow::matrixRegister(const QString &username, const QString &password, const QString &server)
+{
+	qDebug() << "About to register to Matrix";
+	qDebug() << "Username: " << username << " Password: " << password << " Server: " << server;
+}
+
+void MainWindow::showWelcomePage()
+{
+	int index = sliding_stack_->getWidgetIndex(welcome_page_);
+
+	if (sliding_stack_->currentIndex() == sliding_stack_->getWidgetIndex(login_page_))
+		sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT);
+	else
+		sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
+}
+
+void MainWindow::showLoginPage()
+{
+	QSettings settings;
+
+	if (settings.contains("auth/access_token") &&
+	    settings.contains("auth/home_server") &&
+	    settings.contains("auth/user_id")) {
+		QString token = settings.value("auth/access_token").toString();
+		QString home_server = settings.value("auth/home_server").toString();
+		QString user_id = settings.value("auth/user_id").toString();
+
+		showChatPage(user_id, home_server, token);
+
+		return;
+	}
+
+	int index = sliding_stack_->getWidgetIndex(login_page_);
+	sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
+}
+
+void MainWindow::showRegisterPage()
+{
+	int index = sliding_stack_->getWidgetIndex(register_page_);
+	sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT);
+}
+
+MainWindow::~MainWindow()
+{
+	delete ui_;
+}
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
new file mode 100644
index 00000000..5510b6d9
--- /dev/null
+++ b/src/MatrixClient.cc
@@ -0,0 +1,365 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QSettings>
+#include <QUrl>
+#include <QUrlQuery>
+
+#include "Login.h"
+#include "MatrixClient.h"
+#include "Profile.h"
+
+MatrixClient::MatrixClient(QString server, QObject *parent)
+    : QNetworkAccessManager(parent)
+{
+	server_ = "https://" + server;
+	api_url_ = "/_matrix/client/r0";
+	token_ = "";
+
+	QSettings settings;
+	txn_id_ = settings.value("client/transaction_id", 1).toInt();
+
+	// FIXME: Other QNetworkAccessManagers use the finish handler.
+	connect(this, SIGNAL(finished(QNetworkReply *)), this, SLOT(onResponse(QNetworkReply *)));
+}
+
+MatrixClient::~MatrixClient()
+{
+}
+
+void MatrixClient::onVersionsResponse(QNetworkReply *reply)
+{
+	reply->deleteLater();
+
+	qDebug() << "Handling the versions response";
+
+	auto data = reply->readAll();
+	auto json = QJsonDocument::fromJson(data);
+
+	qDebug() << json;
+}
+
+void MatrixClient::onLoginResponse(QNetworkReply *reply)
+{
+	reply->deleteLater();
+
+	int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+	if (status_code == 403) {
+		emit loginError("Wrong username or password");
+		return;
+	}
+
+	if (status_code == 404) {
+		emit loginError("Login endpoint was not found on the server");
+		return;
+	}
+
+	if (status_code != 200) {
+		qDebug() << "Login response: status code " << status_code;
+
+		if (status_code >= 400) {
+			qWarning() << "Login error: " << reply->errorString();
+			emit loginError("An unknown error occured. Please try again.");
+			return;
+		}
+	}
+
+	auto data = reply->readAll();
+	auto json = QJsonDocument::fromJson(data);
+
+	LoginResponse response;
+
+	try {
+		response.deserialize(json);
+		emit loginSuccess(response.getUserId(),
+				  response.getHomeServer(),
+				  response.getAccessToken());
+	} catch (DeserializationException &e) {
+		qWarning() << "Malformed JSON response" << e.what();
+		emit loginError("Malformed response. Possibly not a Matrix server");
+	}
+}
+
+void MatrixClient::onRegisterResponse(QNetworkReply *reply)
+{
+	reply->deleteLater();
+
+	qDebug() << "Handling the register response";
+}
+
+void MatrixClient::onGetOwnProfileResponse(QNetworkReply *reply)
+{
+	reply->deleteLater();
+
+	int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+	if (status >= 400) {
+		qWarning() << reply->errorString();
+		return;
+	}
+
+	auto data = reply->readAll();
+	auto json = QJsonDocument::fromJson(data);
+
+	ProfileResponse response;
+
+	try {
+		response.deserialize(json);
+		emit getOwnProfileResponse(response.getAvatarUrl(), response.getDisplayName());
+	} catch (DeserializationException &e) {
+		qWarning() << "Profile malformed response" << e.what();
+	}
+}
+
+void MatrixClient::onInitialSyncResponse(QNetworkReply *reply)
+{
+	reply->deleteLater();
+
+	int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+	if (status == 0 || status >= 400) {
+		qWarning() << reply->errorString();
+		return;
+	}
+
+	auto data = reply->readAll();
+
+	if (data.isEmpty())
+		return;
+
+	auto json = QJsonDocument::fromJson(data);
+
+	SyncResponse response;
+
+	try {
+		response.deserialize(json);
+		emit initialSyncCompleted(response);
+	} catch (DeserializationException &e) {
+		qWarning() << "Sync malformed response" << e.what();
+	}
+}
+
+void MatrixClient::onSyncResponse(QNetworkReply *reply)
+{
+	reply->deleteLater();
+
+	int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+	if (status == 0 || status >= 400) {
+		qWarning() << reply->errorString();
+		return;
+	}
+
+	auto data = reply->readAll();
+
+	if (data.isEmpty())
+		return;
+
+	auto json = QJsonDocument::fromJson(data);
+
+	SyncResponse response;
+
+	try {
+		response.deserialize(json);
+		emit syncCompleted(response);
+	} catch (DeserializationException &e) {
+		qWarning() << "Sync malformed response" << e.what();
+	}
+}
+
+void MatrixClient::onSendTextMessageResponse(QNetworkReply *reply)
+{
+	reply->deleteLater();
+
+	int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+	if (status == 0 || status >= 400) {
+		qWarning() << reply->errorString();
+		return;
+	}
+
+	auto data = reply->readAll();
+
+	if (data.isEmpty())
+		return;
+
+	auto json = QJsonDocument::fromJson(data);
+
+	if (!json.isObject()) {
+		qDebug() << "Send message response is not a JSON object";
+		return;
+	}
+
+	auto object = json.object();
+
+	if (!object.contains("event_id")) {
+		qDebug() << "SendTextMessage: missnig event_id from response";
+		return;
+	}
+
+	emit messageSent(object.value("event_id").toString(),
+			 reply->property("txn_id").toInt());
+
+	incrementTransactionId();
+}
+
+void MatrixClient::onResponse(QNetworkReply *reply)
+{
+	switch (reply->property("endpoint").toInt()) {
+	case Endpoint::Versions:
+		onVersionsResponse(reply);
+		break;
+	case Endpoint::Login:
+		onLoginResponse(reply);
+		break;
+	case Endpoint::Register:
+		onRegisterResponse(reply);
+	case Endpoint::GetOwnProfile:
+		onGetOwnProfileResponse(reply);
+	case Endpoint::InitialSync:
+		onInitialSyncResponse(reply);
+	case Endpoint::Sync:
+		onSyncResponse(reply);
+	case Endpoint::SendTextMessage:
+		onSendTextMessageResponse(reply);
+	default:
+		break;
+	}
+}
+
+void MatrixClient::login(const QString &username, const QString &password)
+{
+	QUrl endpoint(server_);
+	endpoint.setPath(api_url_ + "/login");
+
+	QNetworkRequest request(endpoint);
+	request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+	LoginRequest body(username, password);
+
+	QNetworkReply *reply = post(request, body.serialize());
+	reply->setProperty("endpoint", Endpoint::Login);
+}
+
+void MatrixClient::sync()
+{
+	QJsonObject filter{{"room",
+			    QJsonObject{{"ephemeral", QJsonObject{{"limit", 0}}}}},
+			   {"presence", QJsonObject{{"limit", 0}}}};
+
+	QUrlQuery query;
+	query.addQueryItem("set_presence", "online");
+	query.addQueryItem("filter", QJsonDocument(filter).toJson(QJsonDocument::Compact));
+	query.addQueryItem("access_token", token_);
+
+	if (next_batch_.isEmpty()) {
+		qDebug() << "Sync requires a valid next_batch token. Initial sync should be performed.";
+		return;
+	}
+
+	query.addQueryItem("since", next_batch_);
+
+	QUrl endpoint(server_);
+	endpoint.setPath(api_url_ + "/sync");
+	endpoint.setQuery(query);
+
+	QNetworkRequest request(QString(endpoint.toEncoded()));
+
+	QNetworkReply *reply = get(request);
+	reply->setProperty("endpoint", Endpoint::Sync);
+}
+
+void MatrixClient::sendTextMessage(QString roomid, QString msg)
+{
+	QUrlQuery query;
+	query.addQueryItem("access_token", token_);
+
+	QUrl endpoint(server_);
+	endpoint.setPath(api_url_ + QString("/rooms/%1/send/m.room.message/%2").arg(roomid).arg(txn_id_));
+	endpoint.setQuery(query);
+
+	QJsonObject body{
+		{"msgtype", "m.text"},
+		{"body", msg}};
+
+	QNetworkRequest request(QString(endpoint.toEncoded()));
+	request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+	QNetworkReply *reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
+
+	reply->setProperty("endpoint", Endpoint::SendTextMessage);
+	reply->setProperty("txn_id", txn_id_);
+}
+
+void MatrixClient::initialSync()
+{
+	QJsonObject filter{{"room",
+			    QJsonObject{{"timeline", QJsonObject{{"limit", 70}}},
+					{"ephemeral", QJsonObject{{"limit", 0}}}}},
+			   {"presence", QJsonObject{{"limit", 0}}}};
+
+	QUrlQuery query;
+	query.addQueryItem("full_state", "true");
+	query.addQueryItem("set_presence", "online");
+	query.addQueryItem("filter", QJsonDocument(filter).toJson(QJsonDocument::Compact));
+	query.addQueryItem("access_token", token_);
+
+	QUrl endpoint(server_);
+	endpoint.setPath(api_url_ + "/sync");
+	endpoint.setQuery(query);
+
+	QNetworkRequest request(QString(endpoint.toEncoded()));
+
+	QNetworkReply *reply = get(request);
+	reply->setProperty("endpoint", Endpoint::InitialSync);
+}
+
+void MatrixClient::versions()
+{
+	QUrl endpoint(server_);
+	endpoint.setPath("/_matrix/client/versions");
+
+	QNetworkRequest request(endpoint);
+
+	QNetworkReply *reply = get(request);
+	reply->setProperty("endpoint", Endpoint::Versions);
+}
+
+void MatrixClient::getOwnProfile()
+{
+	// FIXME: Remove settings from the matrix client. The class should store the user's matrix ID.
+	QSettings settings;
+	auto userid = settings.value("auth/user_id", "").toString();
+
+	QUrlQuery query;
+	query.addQueryItem("access_token", token_);
+
+	QUrl endpoint(server_);
+	endpoint.setPath(api_url_ + "/profile/" + userid);
+	endpoint.setQuery(query);
+
+	QNetworkRequest request(QString(endpoint.toEncoded()));
+
+	QNetworkReply *reply = get(request);
+	reply->setProperty("endpoint", Endpoint::GetOwnProfile);
+}
diff --git a/src/Profile.cc b/src/Profile.cc
new file mode 100644
index 00000000..aa556370
--- /dev/null
+++ b/src/Profile.cc
@@ -0,0 +1,50 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QUrl>
+
+#include "Deserializable.h"
+#include "Profile.h"
+
+QUrl ProfileResponse::getAvatarUrl()
+{
+	return avatar_url_;
+}
+
+QString ProfileResponse::getDisplayName()
+{
+	return display_name_;
+}
+
+void ProfileResponse::deserialize(QJsonDocument data) throw(DeserializationException)
+{
+	if (!data.isObject())
+		throw DeserializationException("Profile response is not a JSON object");
+
+	QJsonObject object = data.object();
+
+	if (object.value("avatar_url") == QJsonValue::Undefined)
+		throw DeserializationException("Profile: missing avatar_url param");
+
+	if (object.value("displayname") == QJsonValue::Undefined)
+		throw DeserializationException("Profile: missing displayname param");
+
+	avatar_url_ = QUrl(object.value("avatar_url").toString());
+	display_name_ = object.value("displayname").toString();
+}
diff --git a/src/RegisterPage.cc b/src/RegisterPage.cc
new file mode 100644
index 00000000..fcb43b86
--- /dev/null
+++ b/src/RegisterPage.cc
@@ -0,0 +1,166 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+#include <QToolTip>
+
+#include "RegisterPage.h"
+
+RegisterPage::RegisterPage(QWidget *parent)
+    : QWidget(parent)
+    , validator_(new InputValidator(parent))
+{
+	top_layout_ = new QVBoxLayout();
+
+	back_layout_ = new QHBoxLayout();
+	back_layout_->setSpacing(0);
+	back_layout_->setContentsMargins(5, 5, -1, -1);
+
+	back_button_ = new FlatButton(this);
+	back_button_->setMinimumSize(QSize(30, 30));
+	back_button_->setCursor(QCursor(Qt::PointingHandCursor));
+
+	QIcon icon;
+	icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off);
+
+	back_button_->setIcon(icon);
+	back_button_->setIconSize(QSize(24, 24));
+
+	back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
+	back_layout_->addStretch(1);
+
+	logo_layout_ = new QHBoxLayout();
+	logo_layout_->setContentsMargins(0, 20, 0, 20);
+	logo_ = new QLabel(this);
+	logo_->setText("nheko");
+	logo_->setStyleSheet("font-size: 22pt; font-weight: 400;");
+	logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
+
+	form_wrapper_ = new QHBoxLayout();
+	form_widget_ = new QWidget();
+	form_widget_->setMinimumSize(QSize(350, 300));
+
+	form_layout_ = new QVBoxLayout();
+	form_layout_->setSpacing(20);
+	form_layout_->setContentsMargins(0, 00, 0, 60);
+	form_widget_->setLayout(form_layout_);
+
+	form_wrapper_->addStretch(1);
+	form_wrapper_->addWidget(form_widget_);
+	form_wrapper_->addStretch(1);
+
+	username_input_ = new TextField();
+	username_input_->setLabel("Username");
+	username_input_->setInkColor("#577275");
+	username_input_->setBackgroundColor("#f9f9f9");
+
+	password_input_ = new TextField();
+	password_input_->setLabel("Password");
+	password_input_->setInkColor("#577275");
+	password_input_->setBackgroundColor("#f9f9f9");
+	password_input_->setEchoMode(QLineEdit::Password);
+
+	password_confirmation_ = new TextField();
+	password_confirmation_->setLabel("Password confirmation");
+	password_confirmation_->setInkColor("#577275");
+	password_confirmation_->setBackgroundColor("#f9f9f9");
+	password_confirmation_->setEchoMode(QLineEdit::Password);
+
+	server_input_ = new TextField();
+	server_input_->setLabel("Home Server");
+	server_input_->setInkColor("#577275");
+	server_input_->setBackgroundColor("#f9f9f9");
+
+	form_layout_->addWidget(username_input_, Qt::AlignHCenter, 0);
+	form_layout_->addWidget(password_input_, Qt::AlignHCenter, 0);
+	form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter, 0);
+	form_layout_->addWidget(server_input_, Qt::AlignHCenter, 0);
+
+	button_layout_ = new QHBoxLayout();
+	button_layout_->setSpacing(0);
+	button_layout_->setContentsMargins(0, 0, 0, 50);
+
+	register_button_ = new RaisedButton("REGISTER", this);
+	register_button_->setBackgroundColor(QColor("#171919"));
+	register_button_->setForegroundColor(QColor("#ebebeb"));
+	register_button_->setMinimumSize(350, 65);
+	register_button_->setCursor(QCursor(Qt::PointingHandCursor));
+	register_button_->setFontSize(17);
+	register_button_->setCornerRadius(3);
+
+	button_layout_->addStretch(1);
+	button_layout_->addWidget(register_button_);
+	button_layout_->addStretch(1);
+
+	top_layout_->addLayout(back_layout_);
+	top_layout_->addStretch(1);
+	top_layout_->addLayout(logo_layout_);
+	top_layout_->addLayout(form_wrapper_);
+	top_layout_->addStretch(2);
+	top_layout_->addLayout(button_layout_);
+	top_layout_->addStretch(1);
+
+	connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
+	connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
+
+	connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
+	connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
+	connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
+	connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
+
+	username_input_->setValidator(validator_->localpart_);
+	password_input_->setValidator(validator_->password_);
+	server_input_->setValidator(validator_->domain_);
+
+	setLayout(top_layout_);
+}
+
+void RegisterPage::onBackButtonClicked()
+{
+	emit backButtonClicked();
+}
+
+void RegisterPage::onRegisterButtonClicked()
+{
+	if (!username_input_->hasAcceptableInput()) {
+		QString text("Invalid username");
+		QPoint point = username_input_->mapToGlobal(username_input_->rect().topRight());
+		QToolTip::showText(point, text);
+	} else if (!password_input_->hasAcceptableInput()) {
+		QString text("Password is not long enough");
+		QPoint point = password_input_->mapToGlobal(password_input_->rect().topRight());
+		QToolTip::showText(point, text);
+	} else if (password_input_->text() != password_confirmation_->text()) {
+		QString text("Passwords don't match");
+		QPoint point = password_confirmation_->mapToGlobal(password_confirmation_->rect().topRight());
+		QToolTip::showText(point, text);
+	} else if (!server_input_->hasAcceptableInput()) {
+		QString text("Invalid server name");
+		QPoint point = server_input_->mapToGlobal(server_input_->rect().topRight());
+		QToolTip::showText(point, text);
+	} else {
+		QString username = username_input_->text();
+		QString password = password_input_->text();
+		QString server = server_input_->text();
+
+		emit registerUser(username, password, server);
+	}
+}
+
+RegisterPage::~RegisterPage()
+{
+}
diff --git a/src/RoomInfo.cc b/src/RoomInfo.cc
new file mode 100644
index 00000000..f8a7c56a
--- /dev/null
+++ b/src/RoomInfo.cc
@@ -0,0 +1,71 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "RoomInfo.h"
+
+RoomInfo::RoomInfo()
+    : name_("")
+    , topic_("")
+{
+}
+
+RoomInfo::RoomInfo(QString name, QString topic, QUrl avatar_url)
+    : name_(name)
+    , topic_(topic)
+    , avatar_url_(avatar_url)
+{
+}
+
+QString RoomInfo::id() const
+{
+	return id_;
+}
+
+QString RoomInfo::name() const
+{
+	return name_;
+}
+
+QString RoomInfo::topic() const
+{
+	return topic_;
+}
+
+QUrl RoomInfo::avatarUrl() const
+{
+	return avatar_url_;
+}
+
+void RoomInfo::setAvatarUrl(const QUrl &url)
+{
+	avatar_url_ = url;
+}
+
+void RoomInfo::setId(const QString &id)
+{
+	id_ = id;
+}
+
+void RoomInfo::setName(const QString &name)
+{
+	name_ = name;
+}
+
+void RoomInfo::setTopic(const QString &topic)
+{
+	topic_ = topic;
+}
diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cc
new file mode 100644
index 00000000..dedae3fd
--- /dev/null
+++ b/src/RoomInfoListItem.cc
@@ -0,0 +1,138 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+
+#include "Ripple.h"
+#include "RoomInfo.h"
+#include "RoomInfoListItem.h"
+
+RoomInfoListItem::RoomInfoListItem(RoomInfo info, QWidget *parent)
+    : QWidget(parent)
+    , info_(info)
+    , is_pressed_(false)
+    , max_height_(65)
+{
+	normal_style_ =
+		"QWidget { background-color: #5d6565; color: #ebebeb;"
+		"border-bottom: 1px solid #171919;}"
+		"QLabel { border: none; }";
+
+	pressed_style_ =
+		"QWidget { background-color: #577275; color: #ebebeb;"
+		"border-bottom: 1px solid #171919;}"
+		"QLabel { border: none; }";
+
+	setStyleSheet(normal_style_);
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+	setAutoFillBackground(true);
+
+	setMinimumSize(parent->width(), max_height_);
+
+	topLayout_ = new QHBoxLayout(this);
+	topLayout_->setSpacing(0);
+	topLayout_->setMargin(0);
+
+	avatarWidget_ = new QWidget(this);
+	avatarWidget_->setMaximumSize(max_height_, max_height_);
+	textWidget_ = new QWidget(this);
+
+	avatarLayout_ = new QVBoxLayout(avatarWidget_);
+	avatarLayout_->setSpacing(0);
+	avatarLayout_->setContentsMargins(0, 5, 0, 5);
+
+	textLayout_ = new QVBoxLayout(textWidget_);
+	textLayout_->setSpacing(0);
+	textLayout_->setContentsMargins(0, 5, 0, 5);
+
+	roomAvatar_ = new Avatar(avatarWidget_);
+	roomAvatar_->setLetter(QChar(info_.name()[0]));
+	avatarLayout_->addWidget(roomAvatar_);
+
+	roomName_ = new QLabel(textWidget_);
+	roomName_->setText(info_.name());
+	roomName_->setMaximumSize(230, max_height_ / 2);
+	roomName_->setStyleSheet("font-weight: 500; font-size: 11.5pt");
+	roomName_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
+
+	roomTopic_ = new QLabel(textWidget_);
+	roomTopic_->setText(info_.topic());
+	roomTopic_->setMaximumSize(230, max_height_ / 2);
+	roomTopic_->setStyleSheet("color: #c9c9c9; font-size: 10pt");
+	roomTopic_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
+
+	textLayout_->addWidget(roomName_);
+	textLayout_->addWidget(roomTopic_);
+
+	topLayout_->addWidget(avatarWidget_);
+	topLayout_->addWidget(textWidget_);
+
+	setElidedText(roomName_, info_.name(), 220);
+	setElidedText(roomTopic_, info_.topic(), 220);
+
+	QPainterPath path;
+	path.addRoundedRect(rect(), 0, 0);
+
+	ripple_overlay_ = new RippleOverlay(this);
+	ripple_overlay_->setClipPath(path);
+	ripple_overlay_->setClipping(true);
+
+	setLayout(topLayout_);
+}
+
+void RoomInfoListItem::setPressedState(bool state)
+{
+	if (!is_pressed_ && state) {
+		is_pressed_ = state;
+		setStyleSheet(pressed_style_);
+	} else if (is_pressed_ && !state) {
+		is_pressed_ = state;
+		setStyleSheet(normal_style_);
+	}
+}
+
+void RoomInfoListItem::mousePressEvent(QMouseEvent *event)
+{
+	emit clicked(info_);
+
+	setPressedState(true);
+
+	// Ripple on mouse position by default.
+	QPoint pos = event->pos();
+	qreal radiusEndValue = static_cast<qreal>(width()) / 2;
+
+	Ripple *ripple = new Ripple(pos);
+
+	ripple->setRadiusEndValue(radiusEndValue);
+	ripple->setOpacityStartValue(0.35);
+	ripple->setColor(QColor("#171919"));
+	ripple->radiusAnimation()->setDuration(300);
+	ripple->opacityAnimation()->setDuration(500);
+
+	ripple_overlay_->addRipple(ripple);
+}
+
+void RoomInfoListItem::setElidedText(QLabel *label, QString text, int width)
+{
+	QFontMetrics metrics(label->font());
+	QString elidedText = metrics.elidedText(text, Qt::ElideRight, width);
+	label->setText(elidedText);
+}
+
+RoomInfoListItem::~RoomInfoListItem()
+{
+}
diff --git a/src/RoomList.cc b/src/RoomList.cc
new file mode 100644
index 00000000..1e147a48
--- /dev/null
+++ b/src/RoomList.cc
@@ -0,0 +1,119 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ui_RoomList.h"
+
+#include <QDebug>
+#include <QJsonArray>
+#include <QLabel>
+
+#include "RoomInfoListItem.h"
+#include "RoomList.h"
+#include "Sync.h"
+
+RoomList::RoomList(QWidget *parent)
+    : QWidget(parent)
+    , ui(new Ui::RoomList)
+{
+	ui->setupUi(this);
+}
+
+RoomList::~RoomList()
+{
+	delete ui;
+}
+
+RoomInfo RoomList::extractRoomInfo(const State &room_state)
+{
+	RoomInfo info;
+
+	auto events = room_state.events();
+
+	for (int i = 0; i < events.count(); i++) {
+		if (events[i].type() == "m.room.name") {
+			info.setName(events[i].content().value("name").toString());
+		} else if (events[i].type() == "m.room.topic") {
+			info.setTopic(events[i].content().value("topic").toString());
+		} else if (events[i].type() == "m.room.avatar") {
+			info.setAvatarUrl(QUrl(events[i].content().value("url").toString()));
+		} else if (events[i].type() == "m.room.canonical_alias") {
+			if (info.name().isEmpty())
+				info.setName(events[i].content().value("alias").toString());
+		}
+	}
+
+	return info;
+}
+
+void RoomList::setInitialRooms(const Rooms &rooms)
+{
+	available_rooms_.clear();
+
+	for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
+		RoomInfo info = RoomList::extractRoomInfo(it.value().state());
+		info.setId(it.key());
+
+		if (info.name().isEmpty())
+			continue;
+
+		if (!info.avatarUrl().isEmpty())
+			emit fetchRoomAvatar(info.id(), info.avatarUrl());
+
+		RoomInfoListItem *room_item = new RoomInfoListItem(info, ui->scrollArea);
+		connect(room_item,
+			SIGNAL(clicked(const RoomInfo &)),
+			this,
+			SLOT(highlightSelectedRoom(const RoomInfo &)));
+
+		available_rooms_.insert(it.key(), room_item);
+
+		ui->scrollVerticalLayout->addWidget(room_item);
+	}
+
+	// TODO: Move this into its own function.
+	auto first_room = available_rooms_.first();
+	first_room->setPressedState(true);
+	emit roomChanged(first_room->info());
+
+	ui->scrollVerticalLayout->addStretch(1);
+}
+
+void RoomList::highlightSelectedRoom(const RoomInfo &info)
+{
+	emit roomChanged(info);
+
+	for (auto it = available_rooms_.constBegin(); it != available_rooms_.constEnd(); it++) {
+		if (it.key() != info.id())
+			it.value()->setPressedState(false);
+	}
+}
+
+void RoomList::updateRoomAvatar(const QString &roomid, const QImage &avatar_image)
+{
+	if (!available_rooms_.contains(roomid)) {
+		qDebug() << "Avatar update on non existent room" << roomid;
+		return;
+	}
+
+	auto list_item = available_rooms_.value(roomid);
+	list_item->setAvatar(avatar_image);
+}
+
+void RoomList::appendRoom(QString name)
+{
+	Q_UNUSED(name);
+}
diff --git a/src/SlidingStackWidget.cc b/src/SlidingStackWidget.cc
new file mode 100644
index 00000000..c4d2f7cf
--- /dev/null
+++ b/src/SlidingStackWidget.cc
@@ -0,0 +1,151 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "SlidingStackWidget.h"
+
+SlidingStackWidget::SlidingStackWidget(QWidget *parent)
+    : QStackedWidget(parent)
+{
+	window_ = parent;
+
+	if (parent == Q_NULLPTR) {
+		qDebug() << "Using nullptr for parent";
+		window_ = this;
+	}
+
+	current_position_ = QPoint(0, 0);
+	speed_ = 400;
+	now_ = 0;
+	next_ = 0;
+	active_ = false;
+	animation_type_ = QEasingCurve::InOutCirc;
+}
+
+SlidingStackWidget::~SlidingStackWidget()
+{
+}
+
+void SlidingStackWidget::slideInNext()
+{
+	int now = currentIndex();
+
+	if (now < count() - 1)
+		slideInIndex(now + 1);
+}
+
+void SlidingStackWidget::slideInPrevious()
+{
+	int now = currentIndex();
+
+	if (now > 0)
+		slideInIndex(now - 1);
+}
+
+void SlidingStackWidget::slideInIndex(int index, AnimationDirection direction)
+{
+	// Take into consideration possible index overflow/undeflow.
+	if (index > count() - 1) {
+		direction = AnimationDirection::RIGHT_TO_LEFT;
+		index = index % count();
+	} else if (index < 0) {
+		direction = AnimationDirection::LEFT_TO_RIGHT;
+		index = (index + count()) % count();
+	}
+
+	slideInWidget(widget(index), direction);
+}
+
+void SlidingStackWidget::slideInWidget(QWidget *next_widget, AnimationDirection direction)
+{
+	// If an animation is currenlty executing we should wait for it to finish before
+	// another transition can start.
+	if (active_)
+		return;
+
+	active_ = true;
+
+	int now = currentIndex();
+	int next = indexOf(next_widget);
+
+	if (now == next) {
+		active_ = false;
+		return;
+	}
+
+	int offset_x = frameRect().width();
+
+	next_widget->setGeometry(0, 0, offset_x, 0);
+
+	if (direction == AnimationDirection::LEFT_TO_RIGHT) {
+		offset_x = -offset_x;
+	}
+
+	QPoint pnext = next_widget->pos();
+	QPoint pnow = widget(now)->pos();
+	current_position_ = pnow;
+
+	// Reposition the next widget outside of the display area.
+	next_widget->move(pnext.x() - offset_x, pnext.y());
+
+	// Make the widget visible.
+	next_widget->show();
+	next_widget->raise();
+
+	// Animate both the next and now widget.
+	QPropertyAnimation *animation_now = new QPropertyAnimation(widget(now), "pos");
+
+	animation_now->setDuration(speed_);
+	animation_now->setEasingCurve(animation_type_);
+	animation_now->setStartValue(QPoint(pnow.x(), pnow.y()));
+	animation_now->setEndValue(QPoint(pnow.x() + offset_x, pnow.y()));
+
+	QPropertyAnimation *animation_next = new QPropertyAnimation(next_widget, "pos");
+
+	animation_next->setDuration(speed_);
+	animation_next->setEasingCurve(animation_type_);
+	animation_next->setStartValue(QPoint(pnext.x() - offset_x, pnext.y()));
+	animation_next->setEndValue(QPoint(pnext.x(), pnext.y()));
+
+	QParallelAnimationGroup *animation_group = new QParallelAnimationGroup;
+
+	animation_group->addAnimation(animation_now);
+	animation_group->addAnimation(animation_next);
+
+	connect(animation_group, SIGNAL(finished()), this, SLOT(onAnimationFinished()));
+
+	next_ = next;
+	now_ = now;
+	animation_group->start();
+}
+
+void SlidingStackWidget::onAnimationFinished()
+{
+	setCurrentIndex(next_);
+
+	// The old widget is no longer necessary so we can hide it and
+	// move it back to its original position.
+	widget(now_)->hide();
+	widget(now_)->move(current_position_);
+
+	active_ = false;
+	emit animationFinished();
+}
+
+int SlidingStackWidget::getWidgetIndex(QWidget *widget)
+{
+	return indexOf(widget);
+}
diff --git a/src/Sync.cc b/src/Sync.cc
new file mode 100644
index 00000000..3ba6d220
--- /dev/null
+++ b/src/Sync.cc
@@ -0,0 +1,290 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonValue>
+
+#include "Deserializable.h"
+#include "Sync.h"
+
+QString SyncResponse::nextBatch() const
+{
+	return next_batch_;
+}
+
+void SyncResponse::deserialize(QJsonDocument data) throw(DeserializationException)
+{
+	if (!data.isObject())
+		throw DeserializationException("Sync response is not a JSON object");
+
+	QJsonObject object = data.object();
+
+	if (object.value("next_batch") == QJsonValue::Undefined)
+		throw DeserializationException("Sync: missing next_batch parameter");
+
+	if (object.value("rooms") == QJsonValue::Undefined)
+		throw DeserializationException("Sync: missing rooms parameter");
+
+	rooms_.deserialize(object.value("rooms"));
+	next_batch_ = object.value("next_batch").toString();
+}
+
+Rooms SyncResponse::rooms() const
+{
+	return rooms_;
+}
+
+QMap<QString, JoinedRoom> Rooms::join() const
+{
+	return join_;
+}
+
+void Rooms::deserialize(QJsonValue data) throw(DeserializationException)
+{
+	if (!data.isObject())
+		throw DeserializationException("Rooms value is not a JSON object");
+
+	QJsonObject object = data.toObject();
+
+	if (!object.contains("join"))
+		throw DeserializationException("rooms/join is missing");
+
+	if (!object.contains("invite"))
+		throw DeserializationException("rooms/invite is missing");
+
+	if (!object.contains("leave"))
+		throw DeserializationException("rooms/leave is missing");
+
+	if (!object.value("join").isObject())
+		throw DeserializationException("rooms/join must be a JSON object");
+
+	if (!object.value("invite").isObject())
+		throw DeserializationException("rooms/invite must be a JSON object");
+
+	if (!object.value("leave").isObject())
+		throw DeserializationException("rooms/leave must be a JSON object");
+
+	auto join = object.value("join").toObject();
+
+	for (auto it = join.constBegin(); it != join.constEnd(); it++) {
+		JoinedRoom tmp_room;
+
+		try {
+			tmp_room.deserialize(it.value());
+			join_.insert(it.key(), tmp_room);
+		} catch (DeserializationException &e) {
+			qWarning() << e.what();
+			qWarning() << "Skipping malformed object for room" << it.key();
+		}
+	}
+}
+
+State JoinedRoom::state() const
+{
+	return state_;
+}
+
+Timeline JoinedRoom::timeline() const
+{
+	return timeline_;
+}
+
+void JoinedRoom::deserialize(QJsonValue data) throw(DeserializationException)
+{
+	if (!data.isObject())
+		throw DeserializationException("JoinedRoom is not a JSON object");
+
+	QJsonObject object = data.toObject();
+
+	if (!object.contains("state"))
+		throw DeserializationException("join/state is missing");
+
+	if (!object.contains("timeline"))
+		throw DeserializationException("join/timeline is missing");
+
+	if (!object.contains("account_data"))
+		throw DeserializationException("join/account_data is missing");
+
+	if (!object.contains("unread_notifications"))
+		throw DeserializationException("join/unread_notifications is missing");
+
+	if (!object.value("state").isObject())
+		throw DeserializationException("join/state should be an object");
+
+	QJsonObject state = object.value("state").toObject();
+
+	if (!state.contains("events"))
+		throw DeserializationException("join/state/events is missing");
+
+	state_.deserialize(state.value("events"));
+	timeline_.deserialize(object.value("timeline"));
+}
+
+QJsonObject Event::content() const
+{
+	return content_;
+}
+
+QJsonObject Event::unsigned_content() const
+{
+	return unsigned_;
+}
+
+QString Event::sender() const
+{
+	return sender_;
+}
+
+QString Event::state_key() const
+{
+	return state_key_;
+}
+
+QString Event::type() const
+{
+	return type_;
+}
+
+QString Event::eventId() const
+{
+	return event_id_;
+}
+
+uint64_t Event::timestamp() const
+{
+	return origin_server_ts_;
+}
+
+void Event::deserialize(QJsonValue data) throw(DeserializationException)
+{
+	if (!data.isObject())
+		throw DeserializationException("Event is not a JSON object");
+
+	QJsonObject object = data.toObject();
+
+	if (!object.contains("content"))
+		throw DeserializationException("event/content is missing");
+
+	if (!object.contains("unsigned"))
+		throw DeserializationException("event/content is missing");
+
+	if (!object.contains("sender"))
+		throw DeserializationException("event/sender is missing");
+
+	if (!object.contains("event_id"))
+		throw DeserializationException("event/event_id is missing");
+
+	// TODO: Make this optional
+	/* if (!object.contains("state_key")) */
+	/* 	throw DeserializationException("event/state_key is missing"); */
+
+	if (!object.contains("type"))
+		throw DeserializationException("event/type is missing");
+
+	if (!object.contains("origin_server_ts"))
+		throw DeserializationException("event/origin_server_ts is missing");
+
+	content_ = object.value("content").toObject();
+	unsigned_ = object.value("unsigned").toObject();
+
+	sender_ = object.value("sender").toString();
+	state_key_ = object.value("state_key").toString();
+	type_ = object.value("type").toString();
+	event_id_ = object.value("event_id").toString();
+
+	origin_server_ts_ = object.value("origin_server_ts").toDouble();
+}
+
+QList<Event> State::events() const
+{
+	return events_;
+}
+
+void State::deserialize(QJsonValue data) throw(DeserializationException)
+{
+	if (!data.isArray())
+		throw DeserializationException("State is not a JSON array");
+
+	QJsonArray event_array = data.toArray();
+
+	for (int i = 0; i < event_array.count(); i++) {
+		Event event;
+
+		try {
+			event.deserialize(event_array.at(i));
+			events_.push_back(event);
+		} catch (DeserializationException &e) {
+			qWarning() << e.what();
+			qWarning() << "Skipping malformed state event";
+		}
+	}
+}
+
+QList<Event> Timeline::events() const
+{
+	return events_;
+}
+
+QString Timeline::previousBatch() const
+{
+	return prev_batch_;
+}
+
+bool Timeline::limited() const
+{
+	return limited_;
+}
+
+void Timeline::deserialize(QJsonValue data) throw(DeserializationException)
+{
+	if (!data.isObject())
+		throw DeserializationException("Timeline is not a JSON object");
+
+	auto object = data.toObject();
+
+	if (!object.contains("events"))
+		throw DeserializationException("timeline/events is missing");
+
+	if (!object.contains("prev_batch"))
+		throw DeserializationException("timeline/prev_batch is missing");
+
+	if (!object.contains("limited"))
+		throw DeserializationException("timeline/limited is missing");
+
+	prev_batch_ = object.value("prev_batch").toString();
+	limited_ = object.value("limited").toBool();
+
+	if (!object.value("events").isArray())
+		throw DeserializationException("timeline/events is not a JSON array");
+
+	auto timeline_events = object.value("events").toArray();
+
+	for (int i = 0; i < timeline_events.count(); i++) {
+		Event event;
+
+		try {
+			event.deserialize(timeline_events.at(i));
+			events_.push_back(event);
+		} catch (DeserializationException &e) {
+			qWarning() << e.what();
+			qWarning() << "Skipping malformed timeline event";
+		}
+	}
+}
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc
new file mode 100644
index 00000000..ec92e77d
--- /dev/null
+++ b/src/TextInputWidget.cc
@@ -0,0 +1,91 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+#include <QPainter>
+#include <QStyleOption>
+
+#include "TextInputWidget.h"
+
+TextInputWidget::TextInputWidget(QWidget *parent)
+    : QWidget(parent)
+{
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+	setCursor(Qt::ArrowCursor);
+	setStyleSheet("background-color: #171919; height: 45px;");
+
+	top_layout_ = new QHBoxLayout();
+	top_layout_->setSpacing(6);
+	top_layout_->setContentsMargins(6, 0, 0, 0);
+
+	send_file_button_ = new FlatButton(this);
+	send_file_button_->setCursor(Qt::PointingHandCursor);
+
+	QIcon send_file_icon;
+	send_file_icon.addFile(":/icons/icons/clip-dark.png", QSize(), QIcon::Normal, QIcon::Off);
+	send_file_button_->setForegroundColor(QColor("#577275"));
+	send_file_button_->setIcon(send_file_icon);
+	send_file_button_->setIconSize(QSize(24, 24));
+
+	input_ = new QLineEdit(this);
+	input_->setPlaceholderText("Write a message...");
+	input_->setStyleSheet("color: #ebebeb; font-size: 10pt; border-radius: 0; padding: 2px; margin-bottom: 4px;");
+
+	send_message_button_ = new FlatButton(this);
+	send_message_button_->setCursor(Qt::PointingHandCursor);
+	send_message_button_->setForegroundColor(QColor("#577275"));
+
+	QIcon send_message_icon;
+	send_message_icon.addFile(":/icons/icons/share-dark.png", QSize(), QIcon::Normal, QIcon::Off);
+	send_message_button_->setIcon(send_message_icon);
+	send_message_button_->setIconSize(QSize(24, 24));
+
+	top_layout_->addWidget(send_file_button_);
+	top_layout_->addWidget(input_);
+	top_layout_->addWidget(send_message_button_);
+
+	setLayout(top_layout_);
+
+	connect(send_message_button_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked()));
+	connect(input_, SIGNAL(returnPressed()), send_message_button_, SIGNAL(clicked()));
+}
+
+void TextInputWidget::onSendButtonClicked()
+{
+	auto msg_text = input_->text();
+
+	if (msg_text.isEmpty())
+		return;
+
+	emit sendTextMessage(msg_text);
+	input_->clear();
+}
+
+void TextInputWidget::paintEvent(QPaintEvent *event)
+{
+	Q_UNUSED(event);
+
+	QStyleOption option;
+	option.initFrom(this);
+
+	QPainter painter(this);
+	style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
+}
+
+TextInputWidget::~TextInputWidget()
+{
+}
diff --git a/src/TopRoomBar.cc b/src/TopRoomBar.cc
new file mode 100644
index 00000000..7e390bdf
--- /dev/null
+++ b/src/TopRoomBar.cc
@@ -0,0 +1,93 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QStyleOption>
+
+#include "TopRoomBar.h"
+
+TopRoomBar::TopRoomBar(QWidget *parent)
+    : QWidget(parent)
+{
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+	setMinimumSize(QSize(0, 70));
+	setStyleSheet("background-color: #171919; color: #ebebeb;");
+
+	top_layout_ = new QHBoxLayout();
+	top_layout_->setSpacing(10);
+	top_layout_->setContentsMargins(10, 10, 0, 10);
+
+	avatar_ = new Avatar(this);
+	avatar_->setLetter(QChar('?'));
+	avatar_->setBackgroundColor(QColor("#ebebeb"));
+	avatar_->setSize(45);
+
+	text_layout_ = new QVBoxLayout();
+	text_layout_->setSpacing(0);
+	text_layout_->setContentsMargins(0, 0, 0, 0);
+
+	name_label_ = new QLabel(this);
+	name_label_->setStyleSheet("font-size: 11pt;");
+
+	topic_label_ = new QLabel(this);
+	topic_label_->setStyleSheet("font-size: 10pt; color: #6c7278;");
+
+	text_layout_->addWidget(name_label_);
+	text_layout_->addWidget(topic_label_);
+
+	settings_button_ = new FlatButton(this);
+	settings_button_->setForegroundColor(QColor("#ebebeb"));
+	settings_button_->setCursor(QCursor(Qt::PointingHandCursor));
+	settings_button_->setStyleSheet("width: 30px; height: 30px;");
+
+	QIcon settings_icon;
+	settings_icon.addFile(":/icons/icons/cog.png", QSize(), QIcon::Normal, QIcon::Off);
+	settings_button_->setIcon(settings_icon);
+	settings_button_->setIconSize(QSize(16, 16));
+
+	search_button_ = new FlatButton(this);
+	search_button_->setForegroundColor(QColor("#ebebeb"));
+	search_button_->setCursor(QCursor(Qt::PointingHandCursor));
+	search_button_->setStyleSheet("width: 30px; height: 30px;");
+
+	QIcon search_icon;
+	search_icon.addFile(":/icons/icons/search.png", QSize(), QIcon::Normal, QIcon::Off);
+	search_button_->setIcon(search_icon);
+	search_button_->setIconSize(QSize(16, 16));
+
+	top_layout_->addWidget(avatar_);
+	top_layout_->addLayout(text_layout_);
+	top_layout_->addStretch(1);
+	top_layout_->addWidget(search_button_);
+	top_layout_->addWidget(settings_button_);
+
+	setLayout(top_layout_);
+}
+
+void TopRoomBar::paintEvent(QPaintEvent *event)
+{
+	Q_UNUSED(event);
+
+	QStyleOption option;
+	option.initFrom(this);
+
+	QPainter painter(this);
+	style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
+}
+
+TopRoomBar::~TopRoomBar()
+{
+}
diff --git a/src/UserInfoWidget.cc b/src/UserInfoWidget.cc
new file mode 100644
index 00000000..a617d212
--- /dev/null
+++ b/src/UserInfoWidget.cc
@@ -0,0 +1,104 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "UserInfoWidget.h"
+#include "FlatButton.h"
+
+UserInfoWidget::UserInfoWidget(QWidget *parent)
+    : QWidget(parent)
+    , display_name_("User")
+    , userid_("@user:homeserver.org")
+{
+	QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+
+	setSizePolicy(sizePolicy);
+	setMinimumSize(QSize(0, 65));
+
+	topLayout_ = new QHBoxLayout(this);
+	topLayout_->setSpacing(0);
+	topLayout_->setContentsMargins(5, 5, 5, 5);
+
+	avatarLayout_ = new QHBoxLayout();
+	textLayout_ = new QVBoxLayout();
+
+	userAvatar_ = new Avatar(this);
+	userAvatar_->setLetter(QChar('?'));
+	userAvatar_->setSize(50);
+	userAvatar_->setMaximumSize(QSize(55, 55));
+
+	displayNameLabel_ = new QLabel(this);
+	displayNameLabel_->setStyleSheet(
+		"padding: 0 9px;"
+		"color: #ebebeb;"
+		"font-size: 11pt;"
+		"margin-bottom: -10px;");
+	displayNameLabel_->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignTop);
+
+	userIdLabel_ = new QLabel(this);
+	userIdLabel_->setStyleSheet(
+		"padding: 0 8px 8px 8px;"
+		"color: #5D6565;"
+		"font-size: 10pt;");
+	userIdLabel_->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter);
+
+	avatarLayout_->addWidget(userAvatar_);
+	textLayout_->addWidget(displayNameLabel_);
+	textLayout_->addWidget(userIdLabel_);
+
+	topLayout_->addLayout(avatarLayout_);
+	topLayout_->addLayout(textLayout_);
+	topLayout_->addStretch(1);
+
+	buttonLayout_ = new QHBoxLayout();
+
+	settingsButton_ = new FlatButton(this);
+	settingsButton_->setForegroundColor(QColor("#ebebeb"));
+	settingsButton_->setCursor(QCursor(Qt::PointingHandCursor));
+	settingsButton_->setStyleSheet("width: 30px; height: 30px;");
+
+	QIcon icon;
+	icon.addFile(":/icons/icons/user-shape.png", QSize(), QIcon::Normal, QIcon::Off);
+
+	settingsButton_->setIcon(icon);
+	settingsButton_->setIconSize(QSize(16, 16));
+
+	buttonLayout_->addWidget(settingsButton_);
+
+	topLayout_->addLayout(buttonLayout_);
+}
+
+UserInfoWidget::~UserInfoWidget()
+{
+}
+
+void UserInfoWidget::setAvatar(const QImage &img)
+{
+	avatar_image_ = img;
+	userAvatar_->setImage(img);
+}
+
+void UserInfoWidget::setDisplayName(const QString &name)
+{
+	display_name_ = name;
+	displayNameLabel_->setText(name);
+}
+
+void UserInfoWidget::setUserId(const QString &userid)
+{
+	userid_ = userid;
+	userIdLabel_->setText(userid);
+}
diff --git a/src/WelcomePage.cc b/src/WelcomePage.cc
new file mode 100644
index 00000000..2220fad7
--- /dev/null
+++ b/src/WelcomePage.cc
@@ -0,0 +1,103 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QApplication>
+#include <QLayout>
+
+#include "WelcomePage.h"
+
+WelcomePage::WelcomePage(QWidget *parent)
+    : QWidget(parent)
+{
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+	top_layout_ = new QVBoxLayout(this);
+	top_layout_->setSpacing(0);
+	top_layout_->setMargin(0);
+
+	intro_banner_ = new QLabel(this);
+	intro_banner_->setStyleSheet("background-color: #1c3133;");
+	intro_banner_->setAlignment(Qt::AlignCenter);
+
+	intro_text_ = new QLabel(this);
+	intro_text_->setText(QApplication::translate("WelcomePage",
+						     "<html>"
+						     "<head/>"
+						     "<body>"
+						     "    <p align=\"center\"><span style=\" font-size:28pt;\"> nheko </span></p>"
+						     "    <p align=\"center\" style=\"margin: 0; line-height: 2pt\">"
+						     "      <span style=\" font-size:12pt; color:#6d7387;\"> "
+						     "		A desktop client for Matrix, the open protocol for decentralized communication."
+						     "	    </span>"
+						     "    </p>\n"
+						     "    <p align=\"center\" style=\"margin: 1pt; line-height: 2pt;\">"
+						     "        <span style=\" font-size:12pt; color:#6d7387;\">Enjoy your stay!</span>"
+						     "    </p>"
+						     "</body>"
+						     "</html>",
+						     Q_NULLPTR));
+
+	top_layout_->addWidget(intro_banner_);
+	top_layout_->addWidget(intro_text_, 0, Qt::AlignCenter);
+
+	button_layout_ = new QHBoxLayout();
+	button_layout_->setSpacing(0);
+	button_layout_->setContentsMargins(0, 20, 0, 80);
+
+	register_button_ = new RaisedButton("REGISTER", this);
+	register_button_->setBackgroundColor(QColor("#171919"));
+	register_button_->setForegroundColor(QColor("#ebebeb"));
+	register_button_->setMinimumSize(240, 60);
+	register_button_->setCursor(QCursor(Qt::PointingHandCursor));
+	register_button_->setFontSize(14);
+	register_button_->setCornerRadius(3);
+
+	login_button_ = new RaisedButton("LOGIN", this);
+	login_button_->setBackgroundColor(QColor("#171919"));
+	login_button_->setForegroundColor(QColor("#ebebeb"));
+	login_button_->setMinimumSize(240, 60);
+	login_button_->setCursor(QCursor(Qt::PointingHandCursor));
+	login_button_->setFontSize(14);
+	login_button_->setCornerRadius(3);
+
+	button_spacer_ = new QSpacerItem(20, 20, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
+
+	button_layout_->addStretch(1);
+	button_layout_->addWidget(register_button_);
+	button_layout_->addItem(button_spacer_);
+	button_layout_->addWidget(login_button_);
+	button_layout_->addStretch(1);
+
+	top_layout_->addLayout(button_layout_);
+
+	connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
+	connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked()));
+}
+
+void WelcomePage::onLoginButtonClicked()
+{
+	emit userLogin();
+}
+
+void WelcomePage::onRegisterButtonClicked()
+{
+	emit userRegister();
+}
+
+WelcomePage::~WelcomePage()
+{
+}
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 00000000..9ef7ffd7
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,48 @@
+/*
+ * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QApplication>
+#include <QFontDatabase>
+
+#include "MainWindow.h"
+
+int main(int argc, char *argv[])
+{
+	QCoreApplication::setApplicationName("nheko");
+	QCoreApplication::setApplicationVersion("Ωμέγa");
+	QCoreApplication::setOrganizationName("Nheko");
+
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-Light.ttf");
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-Regular.ttf");
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-Italic.ttf");
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-Bold.ttf");
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-BoldItalic.ttf");
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-Semibold.ttf");
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-SemiboldItalic.ttf");
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-ExtraBold.ttf");
+	QFontDatabase::addApplicationFont(":/fonts/OpenSans-ExtraBoldItalic.ttf");
+
+	QApplication app(argc, argv);
+
+	QFont font("Open Sans");
+	app.setFont(font);
+
+	MainWindow w;
+	w.show();
+
+	return app.exec();
+}
diff --git a/src/ui/Avatar.cc b/src/ui/Avatar.cc
new file mode 100644
index 00000000..4245c168
--- /dev/null
+++ b/src/ui/Avatar.cc
@@ -0,0 +1,143 @@
+#include <QIcon>
+#include <QPainter>
+#include <QWidget>
+
+#include "Avatar.h"
+
+Avatar::Avatar(QWidget *parent)
+    : QWidget(parent)
+{
+	size_ = ui::AvatarSize;
+	type_ = ui::AvatarType::Letter;
+	letter_ = QChar('A');
+
+	QFont _font(font());
+	_font.setPointSizeF(ui::FontSize);
+	setFont(_font);
+
+	QSizePolicy policy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+	setSizePolicy(policy);
+}
+
+Avatar::~Avatar()
+{
+}
+
+QColor Avatar::textColor() const
+{
+	if (!text_color_.isValid())
+		return QColor("black");
+
+	return text_color_;
+}
+
+QColor Avatar::backgroundColor() const
+{
+	if (!text_color_.isValid())
+		return QColor("white");
+
+	return background_color_;
+}
+
+int Avatar::size() const
+{
+	return size_;
+}
+
+QSize Avatar::sizeHint() const
+{
+	return QSize(size_ + 2, size_ + 2);
+}
+
+void Avatar::setTextColor(const QColor &color)
+{
+	text_color_ = color;
+}
+
+void Avatar::setBackgroundColor(const QColor &color)
+{
+	background_color_ = color;
+}
+
+void Avatar::setSize(int size)
+{
+	size_ = size;
+
+	if (!image_.isNull()) {
+		pixmap_ = QPixmap::fromImage(
+			image_.scaled(size_, size_, Qt::KeepAspectRatio, Qt::SmoothTransformation));
+	}
+
+	QFont _font(font());
+	_font.setPointSizeF(size_ * (ui::FontSize) / 40);
+
+	setFont(_font);
+	update();
+}
+
+void Avatar::setLetter(const QChar &letter)
+{
+	letter_ = letter;
+	type_ = ui::AvatarType::Letter;
+	update();
+}
+
+void Avatar::setImage(const QImage &image)
+{
+	image_ = image;
+	type_ = ui::AvatarType::Image;
+	pixmap_ = QPixmap::fromImage(
+		image_.scaled(size_, size_, Qt::KeepAspectRatio, Qt::SmoothTransformation));
+	update();
+}
+
+void Avatar::setIcon(const QIcon &icon)
+{
+	icon_ = icon;
+	type_ = ui::AvatarType::Icon;
+	update();
+}
+
+void Avatar::paintEvent(QPaintEvent *)
+{
+	QPainter painter(this);
+	painter.setRenderHint(QPainter::Antialiasing);
+
+	QRect r = rect();
+	const int hs = size_ / 2;
+
+	if (type_ != ui::AvatarType::Image) {
+		QBrush brush;
+		brush.setStyle(Qt::SolidPattern);
+		brush.setColor(backgroundColor());
+
+		painter.setPen(Qt::NoPen);
+		painter.setBrush(brush);
+		painter.drawEllipse(r.center(), hs, hs);
+	}
+
+	switch (type_) {
+	case ui::AvatarType::Icon: {
+		icon_.paint(&painter,
+			    QRect((width() - hs) / 2, (height() - hs) / 2, hs, hs),
+			    Qt::AlignCenter,
+			    QIcon::Normal);
+		break;
+	}
+	case ui::AvatarType::Image: {
+		QPainterPath ppath;
+		ppath.addEllipse(width() / 2 - hs, height() / 2 - hs, size_, size_);
+		painter.setClipPath(ppath);
+		painter.drawPixmap(QRect(width() / 2 - hs, height() / 2 - hs, size_, size_), pixmap_);
+		break;
+	}
+	case ui::AvatarType::Letter: {
+		painter.setPen(textColor());
+		painter.setBrush(Qt::NoBrush);
+		painter.drawText(r.translated(0, -1), Qt::AlignCenter, letter_);
+		break;
+	}
+	default:
+		break;
+	}
+}
diff --git a/src/ui/Badge.cc b/src/ui/Badge.cc
new file mode 100644
index 00000000..05531f6c
--- /dev/null
+++ b/src/ui/Badge.cc
@@ -0,0 +1,186 @@
+#include <QPainter>
+
+#include "Badge.h"
+
+Badge::Badge(QWidget *parent)
+    : OverlayWidget(parent)
+{
+	init();
+}
+
+Badge::Badge(const QIcon &icon, QWidget *parent)
+    : OverlayWidget(parent)
+{
+	init();
+	setIcon(icon);
+}
+
+Badge::Badge(const QString &text, QWidget *parent)
+    : OverlayWidget(parent)
+{
+	init();
+	setText(text);
+}
+
+Badge::~Badge()
+{
+}
+
+void Badge::init()
+{
+	x_ = 0;
+	y_ = 0;
+	padding_ = 10;
+
+	setAttribute(Qt::WA_TransparentForMouseEvents);
+
+	QFont _font(font());
+	_font.setPointSizeF(10);
+	_font.setStyleName("Bold");
+
+	setFont(_font);
+	setText("");
+}
+
+QString Badge::text() const
+{
+	return text_;
+}
+
+QIcon Badge::icon() const
+{
+	return icon_;
+}
+
+QSize Badge::sizeHint() const
+{
+	const int d = getDiameter();
+	return QSize(d + 4, d + 4);
+}
+
+qreal Badge::relativeYPosition() const
+{
+	return y_;
+}
+
+qreal Badge::relativeXPosition() const
+{
+	return x_;
+}
+
+QPointF Badge::relativePosition() const
+{
+	return QPointF(x_, y_);
+}
+
+QColor Badge::backgroundColor() const
+{
+	if (!background_color_.isValid())
+		return QColor("black");
+
+	return background_color_;
+}
+
+QColor Badge::textColor() const
+{
+	if (!text_color_.isValid())
+		return QColor("white");
+
+	return text_color_;
+}
+
+void Badge::setTextColor(const QColor &color)
+{
+	text_color_ = color;
+}
+
+void Badge::setBackgroundColor(const QColor &color)
+{
+	background_color_ = color;
+}
+
+void Badge::setRelativePosition(const QPointF &pos)
+{
+	setRelativePosition(pos.x(), pos.y());
+}
+
+void Badge::setRelativePosition(qreal x, qreal y)
+{
+	x_ = x;
+	y_ = y;
+	update();
+}
+
+void Badge::setRelativeXPosition(qreal x)
+{
+	x_ = x;
+	update();
+}
+
+void Badge::setRelativeYPosition(qreal y)
+{
+	y_ = y;
+	update();
+}
+
+void Badge::setIcon(const QIcon &icon)
+{
+	icon_ = icon;
+	update();
+}
+
+void Badge::setText(const QString &text)
+{
+	text_ = text;
+
+	if (!icon_.isNull())
+		icon_ = QIcon();
+
+	size_ = fontMetrics().size(Qt::TextShowMnemonic, text);
+
+	update();
+}
+
+void Badge::paintEvent(QPaintEvent *)
+{
+	QPainter painter(this);
+	painter.setRenderHint(QPainter::Antialiasing);
+	painter.translate(x_, y_);
+
+	QBrush brush;
+	brush.setStyle(Qt::SolidPattern);
+	brush.setColor(isEnabled() ? backgroundColor() : QColor("#cccccc"));
+
+	painter.setBrush(brush);
+	painter.setPen(Qt::NoPen);
+
+	const int d = getDiameter();
+
+	QRectF r(0, 0, d, d);
+	r.translate(QPointF((width() - d), (height() - d)) / 2);
+
+	if (icon_.isNull()) {
+		painter.drawEllipse(r);
+		painter.setPen(textColor());
+		painter.setBrush(Qt::NoBrush);
+		painter.drawText(r.translated(0, -0.5), Qt::AlignCenter, text_);
+	} else {
+		painter.drawEllipse(r);
+		QRectF q(0, 0, 16, 16);
+		q.moveCenter(r.center());
+		QPixmap pixmap = icon().pixmap(16, 16);
+		QPainter icon(&pixmap);
+		icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
+		icon.fillRect(pixmap.rect(), textColor());
+		painter.drawPixmap(q.toRect(), pixmap);
+	}
+}
+
+int Badge::getDiameter() const
+{
+	if (icon_.isNull()) {
+		return qMax(size_.width(), size_.height()) + padding_;
+	}
+	// FIXME: Move this to Theme.h as the default
+	return 24;
+}
diff --git a/src/ui/FlatButton.cc b/src/ui/FlatButton.cc
new file mode 100644
index 00000000..97711de5
--- /dev/null
+++ b/src/ui/FlatButton.cc
@@ -0,0 +1,761 @@
+
+#include <QBitmap>
+#include <QEventTransition>
+#include <QFontDatabase>
+#include <QIcon>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QPainterPath>
+#include <QResizeEvent>
+#include <QSequentialAnimationGroup>
+#include <QSignalTransition>
+
+#include "FlatButton.h"
+#include "Ripple.h"
+#include "RippleOverlay.h"
+#include "ThemeManager.h"
+
+void FlatButton::init()
+{
+	ripple_overlay_ = new RippleOverlay(this);
+	state_machine_ = new FlatButtonStateMachine(this);
+	role_ = ui::Default;
+	ripple_style_ = ui::PositionedRipple;
+	icon_placement_ = ui::LeftIcon;
+	overlay_style_ = ui::GrayOverlay;
+	bg_mode_ = Qt::TransparentMode;
+	fixed_ripple_radius_ = 64;
+	corner_radius_ = 3;
+	base_opacity_ = 0.13;
+	font_size_ = 10;  // 10.5;
+	use_fixed_ripple_radius_ = false;
+	halo_visible_ = false;
+
+	setStyle(&ThemeManager::instance());
+	setAttribute(Qt::WA_Hover);
+	setMouseTracking(true);
+
+	QPainterPath path;
+	path.addRoundedRect(rect(), corner_radius_, corner_radius_);
+
+	ripple_overlay_->setClipPath(path);
+	ripple_overlay_->setClipping(true);
+
+	state_machine_->setupProperties();
+	state_machine_->startAnimations();
+}
+
+FlatButton::FlatButton(QWidget *parent, ui::ButtonPreset preset)
+    : QPushButton(parent)
+{
+	init();
+	applyPreset(preset);
+}
+
+FlatButton::FlatButton(const QString &text, QWidget *parent, ui::ButtonPreset preset)
+    : QPushButton(text, parent)
+{
+	init();
+	applyPreset(preset);
+}
+
+FlatButton::FlatButton(const QString &text, ui::Role role, QWidget *parent, ui::ButtonPreset preset)
+    : QPushButton(text, parent)
+{
+	init();
+	applyPreset(preset);
+	setRole(role);
+}
+
+FlatButton::~FlatButton()
+{
+}
+
+void FlatButton::applyPreset(ui::ButtonPreset preset)
+{
+	switch (preset) {
+	case ui::FlatPreset:
+		setOverlayStyle(ui::NoOverlay);
+		break;
+	case ui::CheckablePreset:
+		setOverlayStyle(ui::NoOverlay);
+		setCheckable(true);
+		setHaloVisible(false);
+		break;
+	default:
+		break;
+	}
+}
+
+void FlatButton::setRole(ui::Role role)
+{
+	role_ = role;
+	state_machine_->setupProperties();
+}
+
+ui::Role FlatButton::role() const
+{
+	return role_;
+}
+
+void FlatButton::setForegroundColor(const QColor &color)
+{
+	foreground_color_ = color;
+}
+
+QColor FlatButton::foregroundColor() const
+{
+	if (!foreground_color_.isValid()) {
+		if (bg_mode_ == Qt::OpaqueMode) {
+			return ThemeManager::instance().themeColor("BrightWhite");
+		}
+
+		switch (role_) {
+		case ui::Primary:
+			return ThemeManager::instance().themeColor("Blue");
+		case ui::Secondary:
+			return ThemeManager::instance().themeColor("Gray");
+		case ui::Default:
+		default:
+			return ThemeManager::instance().themeColor("Black");
+		}
+	}
+
+	return foreground_color_;
+}
+
+void FlatButton::setBackgroundColor(const QColor &color)
+{
+	background_color_ = color;
+}
+
+QColor FlatButton::backgroundColor() const
+{
+	if (!background_color_.isValid()) {
+		switch (role_) {
+		case ui::Primary:
+			return ThemeManager::instance().themeColor("Blue");
+		case ui::Secondary:
+			return ThemeManager::instance().themeColor("Gray");
+		case ui::Default:
+		default:
+			return ThemeManager::instance().themeColor("Black");
+		}
+	}
+
+	return background_color_;
+}
+
+void FlatButton::setOverlayColor(const QColor &color)
+{
+	overlay_color_ = color;
+	setOverlayStyle(ui::TintedOverlay);
+}
+
+QColor FlatButton::overlayColor() const
+{
+	if (!overlay_color_.isValid()) {
+		return foregroundColor();
+	}
+
+	return overlay_color_;
+}
+
+void FlatButton::setDisabledForegroundColor(const QColor &color)
+{
+	disabled_color_ = color;
+}
+
+QColor FlatButton::disabledForegroundColor() const
+{
+	if (!disabled_color_.isValid()) {
+		return ThemeManager::instance().themeColor("FadedWhite");
+	}
+
+	return disabled_color_;
+}
+
+void FlatButton::setDisabledBackgroundColor(const QColor &color)
+{
+	disabled_background_color_ = color;
+}
+
+QColor FlatButton::disabledBackgroundColor() const
+{
+	if (!disabled_background_color_.isValid()) {
+		return ThemeManager::instance().themeColor("FadedWhite");
+	}
+
+	return disabled_background_color_;
+}
+
+void FlatButton::setFontSize(qreal size)
+{
+	font_size_ = size;
+
+	QFont f(font());
+	f.setPointSizeF(size);
+	setFont(f);
+
+	update();
+}
+
+qreal FlatButton::fontSize() const
+{
+	return font_size_;
+}
+
+void FlatButton::setHaloVisible(bool visible)
+{
+	halo_visible_ = visible;
+	update();
+}
+
+bool FlatButton::isHaloVisible() const
+{
+	return halo_visible_;
+}
+
+void FlatButton::setOverlayStyle(ui::OverlayStyle style)
+{
+	overlay_style_ = style;
+	update();
+}
+
+ui::OverlayStyle FlatButton::overlayStyle() const
+{
+	return overlay_style_;
+}
+
+void FlatButton::setRippleStyle(ui::RippleStyle style)
+{
+	ripple_style_ = style;
+}
+
+ui::RippleStyle FlatButton::rippleStyle() const
+{
+	return ripple_style_;
+}
+
+void FlatButton::setIconPlacement(ui::ButtonIconPlacement placement)
+{
+	icon_placement_ = placement;
+	update();
+}
+
+ui::ButtonIconPlacement FlatButton::iconPlacement() const
+{
+	return icon_placement_;
+}
+
+void FlatButton::setCornerRadius(qreal radius)
+{
+	corner_radius_ = radius;
+	updateClipPath();
+	update();
+}
+
+qreal FlatButton::cornerRadius() const
+{
+	return corner_radius_;
+}
+
+void FlatButton::setBackgroundMode(Qt::BGMode mode)
+{
+	bg_mode_ = mode;
+	state_machine_->setupProperties();
+}
+
+Qt::BGMode FlatButton::backgroundMode() const
+{
+	return bg_mode_;
+}
+
+void FlatButton::setBaseOpacity(qreal opacity)
+{
+	base_opacity_ = opacity;
+	state_machine_->setupProperties();
+}
+
+qreal FlatButton::baseOpacity() const
+{
+	return base_opacity_;
+}
+
+void FlatButton::setCheckable(bool value)
+{
+	state_machine_->updateCheckedStatus();
+	state_machine_->setCheckedOverlayProgress(0);
+
+	QPushButton::setCheckable(value);
+}
+
+void FlatButton::setHasFixedRippleRadius(bool value)
+{
+	use_fixed_ripple_radius_ = value;
+}
+
+bool FlatButton::hasFixedRippleRadius() const
+{
+	return use_fixed_ripple_radius_;
+}
+
+void FlatButton::setFixedRippleRadius(qreal radius)
+{
+	fixed_ripple_radius_ = radius;
+	setHasFixedRippleRadius(true);
+}
+
+QSize FlatButton::sizeHint() const
+{
+	ensurePolished();
+
+	QSize label(fontMetrics().size(Qt::TextSingleLine, text()));
+
+	int w = 20 + label.width();
+	int h = label.height();
+
+	if (!icon().isNull()) {
+		w += iconSize().width() + FlatButton::IconPadding;
+		h = qMax(h, iconSize().height());
+	}
+
+	return QSize(w, 20 + h);
+}
+
+void FlatButton::checkStateSet()
+{
+	state_machine_->updateCheckedStatus();
+	QPushButton::checkStateSet();
+}
+
+void FlatButton::mousePressEvent(QMouseEvent *event)
+{
+	if (ui::NoRipple != ripple_style_) {
+		QPoint pos;
+		qreal radiusEndValue;
+
+		if (ui::CenteredRipple == ripple_style_) {
+			pos = rect().center();
+		} else {
+			pos = event->pos();
+		}
+
+		if (use_fixed_ripple_radius_) {
+			radiusEndValue = fixed_ripple_radius_;
+		} else {
+			radiusEndValue = static_cast<qreal>(width()) / 2;
+		}
+
+		Ripple *ripple = new Ripple(pos);
+
+		ripple->setRadiusEndValue(radiusEndValue);
+		ripple->setOpacityStartValue(0.35);
+		ripple->setColor(foregroundColor());
+		ripple->radiusAnimation()->setDuration(600);
+		ripple->opacityAnimation()->setDuration(1300);
+
+		ripple_overlay_->addRipple(ripple);
+	}
+
+	QPushButton::mousePressEvent(event);
+}
+
+void FlatButton::mouseReleaseEvent(QMouseEvent *event)
+{
+	QPushButton::mouseReleaseEvent(event);
+	state_machine_->updateCheckedStatus();
+}
+
+void FlatButton::resizeEvent(QResizeEvent *event)
+{
+	QPushButton::resizeEvent(event);
+	updateClipPath();
+}
+
+void FlatButton::paintEvent(QPaintEvent *event)
+{
+	Q_UNUSED(event)
+
+	QPainter painter(this);
+	painter.setRenderHint(QPainter::Antialiasing);
+
+	const qreal cr = corner_radius_;
+
+	if (cr > 0) {
+		QPainterPath path;
+		path.addRoundedRect(rect(), cr, cr);
+
+		painter.setClipPath(path);
+		painter.setClipping(true);
+	}
+
+	paintBackground(&painter);
+	paintHalo(&painter);
+
+	painter.setOpacity(1);
+	painter.setClipping(false);
+
+	paintForeground(&painter);
+}
+
+void FlatButton::paintBackground(QPainter *painter)
+{
+	const qreal overlayOpacity = state_machine_->overlayOpacity();
+	const qreal checkedProgress = state_machine_->checkedOverlayProgress();
+
+	if (Qt::OpaqueMode == bg_mode_) {
+		QBrush brush;
+		brush.setStyle(Qt::SolidPattern);
+
+		if (isEnabled()) {
+			brush.setColor(backgroundColor());
+		} else {
+			brush.setColor(disabledBackgroundColor());
+		}
+
+		painter->setOpacity(1);
+		painter->setBrush(brush);
+		painter->setPen(Qt::NoPen);
+		painter->drawRect(rect());
+	}
+
+	QBrush brush;
+	brush.setStyle(Qt::SolidPattern);
+	painter->setPen(Qt::NoPen);
+
+	if (!isEnabled()) {
+		return;
+	}
+
+	if ((ui::NoOverlay != overlay_style_) && (overlayOpacity > 0)) {
+		if (ui::TintedOverlay == overlay_style_) {
+			brush.setColor(overlayColor());
+		} else {
+			brush.setColor(Qt::gray);
+		}
+
+		painter->setOpacity(overlayOpacity);
+		painter->setBrush(brush);
+		painter->drawRect(rect());
+	}
+
+	if (isCheckable() && checkedProgress > 0) {
+		const qreal q = Qt::TransparentMode == bg_mode_ ? 0.45 : 0.7;
+		brush.setColor(foregroundColor());
+		painter->setOpacity(q * checkedProgress);
+		painter->setBrush(brush);
+		QRect r(rect());
+		r.setHeight(static_cast<qreal>(r.height()) * checkedProgress);
+		painter->drawRect(r);
+	}
+}
+
+void FlatButton::paintHalo(QPainter *painter)
+{
+	if (!halo_visible_)
+		return;
+
+	const qreal opacity = state_machine_->haloOpacity();
+	const qreal s = state_machine_->haloScaleFactor() * state_machine_->haloSize();
+	const qreal radius = static_cast<qreal>(width()) * s;
+
+	if (isEnabled() && opacity > 0) {
+		QBrush brush;
+		brush.setStyle(Qt::SolidPattern);
+		brush.setColor(foregroundColor());
+		painter->setOpacity(opacity);
+		painter->setBrush(brush);
+		painter->setPen(Qt::NoPen);
+		const QPointF center = rect().center();
+		painter->drawEllipse(center, radius, radius);
+	}
+}
+
+#define COLOR_INTERPOLATE(CH) (1 - progress) * source.CH() + progress *dest.CH()
+
+void FlatButton::paintForeground(QPainter *painter)
+{
+	if (isEnabled()) {
+		painter->setPen(foregroundColor());
+		const qreal progress = state_machine_->checkedOverlayProgress();
+
+		if (isCheckable() && progress > 0) {
+			QColor source = foregroundColor();
+			QColor dest = Qt::TransparentMode == bg_mode_ ? Qt::white
+								      : backgroundColor();
+			if (qFuzzyCompare(1, progress)) {
+				painter->setPen(dest);
+			} else {
+				painter->setPen(QColor(COLOR_INTERPOLATE(red),
+						       COLOR_INTERPOLATE(green),
+						       COLOR_INTERPOLATE(blue),
+						       COLOR_INTERPOLATE(alpha)));
+			}
+		}
+	} else {
+		painter->setPen(disabledForegroundColor());
+	}
+
+	if (icon().isNull()) {
+		painter->drawText(rect(), Qt::AlignCenter, text());
+		return;
+	}
+
+	QSize textSize(fontMetrics().size(Qt::TextSingleLine, text()));
+	QSize base(size() - textSize);
+
+	const int iw = iconSize().width() + IconPadding;
+	QPoint pos((base.width() - iw) / 2, 0);
+
+	QRect textGeometry(pos + QPoint(0, base.height() / 2), textSize);
+	QRect iconGeometry(pos + QPoint(0, (height() - iconSize().height()) / 2), iconSize());
+
+	if (ui::LeftIcon == icon_placement_) {
+		textGeometry.translate(iw, 0);
+	} else {
+		iconGeometry.translate(textSize.width() + IconPadding, 0);
+	}
+
+	painter->drawText(textGeometry, Qt::AlignCenter, text());
+
+	QPixmap pixmap = icon().pixmap(iconSize());
+	QPainter icon(&pixmap);
+	icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
+	icon.fillRect(pixmap.rect(), painter->pen().color());
+	painter->drawPixmap(iconGeometry, pixmap);
+}
+
+void FlatButton::updateClipPath()
+{
+	const qreal radius = corner_radius_;
+
+	QPainterPath path;
+	path.addRoundedRect(rect(), radius, radius);
+	ripple_overlay_->setClipPath(path);
+}
+
+FlatButtonStateMachine::FlatButtonStateMachine(FlatButton *parent)
+    : QStateMachine(parent)
+    , button_(parent)
+    , top_level_state_(new QState(QState::ParallelStates))
+    , config_state_(new QState(top_level_state_))
+    , checkable_state_(new QState(top_level_state_))
+    , checked_state_(new QState(checkable_state_))
+    , unchecked_state_(new QState(checkable_state_))
+    , neutral_state_(new QState(config_state_))
+    , neutral_focused_state_(new QState(config_state_))
+    , hovered_state_(new QState(config_state_))
+    , hovered_focused_state_(new QState(config_state_))
+    , pressed_state_(new QState(config_state_))
+    , halo_animation_(new QSequentialAnimationGroup(this))
+    , overlay_opacity_(0)
+    , checked_overlay_progress_(parent->isChecked() ? 1 : 0)
+    , halo_opacity_(0)
+    , halo_size_(0.8)
+    , halo_scale_factor_(1)
+    , was_checked_(false)
+{
+	Q_ASSERT(parent);
+
+	parent->installEventFilter(this);
+
+	config_state_->setInitialState(neutral_state_);
+	addState(top_level_state_);
+	setInitialState(top_level_state_);
+
+	checkable_state_->setInitialState(parent->isChecked() ? checked_state_
+							      : unchecked_state_);
+	QSignalTransition *transition;
+	QPropertyAnimation *animation;
+
+	transition = new QSignalTransition(this, SIGNAL(buttonChecked()));
+	transition->setTargetState(checked_state_);
+	unchecked_state_->addTransition(transition);
+
+	animation = new QPropertyAnimation(this, "checkedOverlayProgress", this);
+	animation->setDuration(200);
+	transition->addAnimation(animation);
+
+	transition = new QSignalTransition(this, SIGNAL(buttonUnchecked()));
+	transition->setTargetState(unchecked_state_);
+	checked_state_->addTransition(transition);
+
+	animation = new QPropertyAnimation(this, "checkedOverlayProgress", this);
+	animation->setDuration(200);
+	transition->addAnimation(animation);
+
+	addTransition(button_, QEvent::FocusIn, neutral_state_, neutral_focused_state_);
+	addTransition(button_, QEvent::FocusOut, neutral_focused_state_, neutral_state_);
+	addTransition(button_, QEvent::Enter, neutral_state_, hovered_state_);
+	addTransition(button_, QEvent::Leave, hovered_state_, neutral_state_);
+	addTransition(button_, QEvent::Enter, neutral_focused_state_, hovered_focused_state_);
+	addTransition(button_, QEvent::Leave, hovered_focused_state_, neutral_focused_state_);
+	addTransition(button_, QEvent::FocusIn, hovered_state_, hovered_focused_state_);
+	addTransition(button_, QEvent::FocusOut, hovered_focused_state_, hovered_state_);
+	addTransition(this, SIGNAL(buttonPressed()), hovered_state_, pressed_state_);
+	addTransition(button_, QEvent::Leave, pressed_state_, neutral_focused_state_);
+	addTransition(button_, QEvent::FocusOut, pressed_state_, hovered_state_);
+
+	neutral_state_->assignProperty(this, "haloSize", 0);
+	neutral_focused_state_->assignProperty(this, "haloSize", 0.7);
+	hovered_state_->assignProperty(this, "haloSize", 0);
+	pressed_state_->assignProperty(this, "haloSize", 4);
+	hovered_focused_state_->assignProperty(this, "haloSize", 0.7);
+
+	QPropertyAnimation *grow = new QPropertyAnimation(this);
+	QPropertyAnimation *shrink = new QPropertyAnimation(this);
+
+	grow->setTargetObject(this);
+	grow->setPropertyName("haloScaleFactor");
+	grow->setStartValue(0.56);
+	grow->setEndValue(0.63);
+	grow->setEasingCurve(QEasingCurve::InOutSine);
+	grow->setDuration(840);
+
+	shrink->setTargetObject(this);
+	shrink->setPropertyName("haloScaleFactor");
+	shrink->setStartValue(0.63);
+	shrink->setEndValue(0.56);
+	shrink->setEasingCurve(QEasingCurve::InOutSine);
+	shrink->setDuration(840);
+
+	halo_animation_->addAnimation(grow);
+	halo_animation_->addAnimation(shrink);
+	halo_animation_->setLoopCount(-1);
+}
+
+FlatButtonStateMachine::~FlatButtonStateMachine()
+{
+}
+
+void FlatButtonStateMachine::setOverlayOpacity(qreal opacity)
+{
+	overlay_opacity_ = opacity;
+	button_->update();
+}
+
+void FlatButtonStateMachine::setCheckedOverlayProgress(qreal opacity)
+{
+	checked_overlay_progress_ = opacity;
+	button_->update();
+}
+
+void FlatButtonStateMachine::setHaloOpacity(qreal opacity)
+{
+	halo_opacity_ = opacity;
+	button_->update();
+}
+
+void FlatButtonStateMachine::setHaloSize(qreal size)
+{
+	halo_size_ = size;
+	button_->update();
+}
+
+void FlatButtonStateMachine::setHaloScaleFactor(qreal factor)
+{
+	halo_scale_factor_ = factor;
+	button_->update();
+}
+
+void FlatButtonStateMachine::startAnimations()
+{
+	halo_animation_->start();
+	start();
+}
+
+void FlatButtonStateMachine::setupProperties()
+{
+	QColor overlayColor;
+
+	if (Qt::TransparentMode == button_->backgroundMode()) {
+		overlayColor = button_->backgroundColor();
+	} else {
+		overlayColor = button_->foregroundColor();
+	}
+
+	const qreal baseOpacity = button_->baseOpacity();
+
+	neutral_state_->assignProperty(this, "overlayOpacity", 0);
+	neutral_state_->assignProperty(this, "haloOpacity", 0);
+	neutral_focused_state_->assignProperty(this, "overlayOpacity", 0);
+	neutral_focused_state_->assignProperty(this, "haloOpacity", baseOpacity);
+	hovered_state_->assignProperty(this, "overlayOpacity", baseOpacity);
+	hovered_state_->assignProperty(this, "haloOpacity", 0);
+	hovered_focused_state_->assignProperty(this, "overlayOpacity", baseOpacity);
+	hovered_focused_state_->assignProperty(this, "haloOpacity", baseOpacity);
+	pressed_state_->assignProperty(this, "overlayOpacity", baseOpacity);
+	pressed_state_->assignProperty(this, "haloOpacity", 0);
+	checked_state_->assignProperty(this, "checkedOverlayProgress", 1);
+	unchecked_state_->assignProperty(this, "checkedOverlayProgress", 0);
+
+	button_->update();
+}
+
+void FlatButtonStateMachine::updateCheckedStatus()
+{
+	const bool checked = button_->isChecked();
+	if (was_checked_ != checked) {
+		was_checked_ = checked;
+		if (checked) {
+			emit buttonChecked();
+		} else {
+			emit buttonUnchecked();
+		}
+	}
+}
+
+bool FlatButtonStateMachine::eventFilter(QObject *watched,
+					 QEvent *event)
+{
+	if (QEvent::FocusIn == event->type()) {
+		QFocusEvent *focusEvent = static_cast<QFocusEvent *>(event);
+		if (focusEvent && Qt::MouseFocusReason == focusEvent->reason()) {
+			emit buttonPressed();
+			return true;
+		}
+	}
+
+	return QStateMachine::eventFilter(watched, event);
+}
+
+void FlatButtonStateMachine::addTransition(QObject *object,
+					   const char *signal,
+					   QState *fromState,
+					   QState *toState)
+{
+	addTransition(new QSignalTransition(object, signal), fromState, toState);
+}
+
+void FlatButtonStateMachine::addTransition(QObject *object,
+					   QEvent::Type eventType,
+					   QState *fromState,
+					   QState *toState)
+{
+	addTransition(new QEventTransition(object, eventType), fromState, toState);
+}
+
+void FlatButtonStateMachine::addTransition(QAbstractTransition *transition,
+					   QState *fromState,
+					   QState *toState)
+{
+	transition->setTargetState(toState);
+
+	QPropertyAnimation *animation;
+
+	animation = new QPropertyAnimation(this, "overlayOpacity", this);
+	animation->setDuration(150);
+	transition->addAnimation(animation);
+
+	animation = new QPropertyAnimation(this, "haloOpacity", this);
+	animation->setDuration(170);
+	transition->addAnimation(animation);
+
+	animation = new QPropertyAnimation(this, "haloSize", this);
+	animation->setDuration(350);
+	animation->setEasingCurve(QEasingCurve::OutCubic);
+	transition->addAnimation(animation);
+
+	fromState->addTransition(transition);
+}
diff --git a/src/ui/OverlayWidget.cc b/src/ui/OverlayWidget.cc
new file mode 100644
index 00000000..b4dfb918
--- /dev/null
+++ b/src/ui/OverlayWidget.cc
@@ -0,0 +1,59 @@
+#include "OverlayWidget.h"
+#include <QEvent>
+
+OverlayWidget::OverlayWidget(QWidget *parent)
+    : QWidget(parent)
+{
+	if (parent)
+		parent->installEventFilter(this);
+}
+
+OverlayWidget::~OverlayWidget()
+{
+}
+
+bool OverlayWidget::event(QEvent *event)
+{
+	if (!parent())
+		return QWidget::event(event);
+
+	switch (event->type()) {
+	case QEvent::ParentChange: {
+		parent()->installEventFilter(this);
+		setGeometry(overlayGeometry());
+		break;
+	}
+	case QEvent::ParentAboutToChange: {
+		parent()->removeEventFilter(this);
+		break;
+	}
+	default:
+		break;
+	}
+
+	return QWidget::event(event);
+}
+
+bool OverlayWidget::eventFilter(QObject *obj, QEvent *event)
+{
+	switch (event->type()) {
+	case QEvent::Move:
+	case QEvent::Resize:
+		setGeometry(overlayGeometry());
+		break;
+	default:
+		break;
+	}
+
+	return QWidget::eventFilter(obj, event);
+}
+
+QRect OverlayWidget::overlayGeometry() const
+{
+	QWidget *widget = parentWidget();
+
+	if (!widget)
+		return QRect();
+
+	return widget->rect();
+}
diff --git a/src/ui/RaisedButton.cc b/src/ui/RaisedButton.cc
new file mode 100644
index 00000000..74f549c4
--- /dev/null
+++ b/src/ui/RaisedButton.cc
@@ -0,0 +1,92 @@
+#include <QEventTransition>
+#include <QGraphicsDropShadowEffect>
+#include <QPropertyAnimation>
+#include <QState>
+#include <QStateMachine>
+
+#include "RaisedButton.h"
+
+void RaisedButton::init()
+{
+	shadow_state_machine_ = new QStateMachine(this);
+	normal_state_ = new QState;
+	pressed_state_ = new QState;
+	effect_ = new QGraphicsDropShadowEffect;
+
+	effect_->setBlurRadius(7);
+	effect_->setOffset(QPointF(0, 2));
+	effect_->setColor(QColor(0, 0, 0, 75));
+
+	setBackgroundMode(Qt::OpaqueMode);
+	setMinimumHeight(42);
+	setGraphicsEffect(effect_);
+	setBaseOpacity(0.3);
+
+	shadow_state_machine_->addState(normal_state_);
+	shadow_state_machine_->addState(pressed_state_);
+
+	normal_state_->assignProperty(effect_, "offset", QPointF(0, 2));
+	normal_state_->assignProperty(effect_, "blurRadius", 7);
+
+	pressed_state_->assignProperty(effect_, "offset", QPointF(0, 5));
+	pressed_state_->assignProperty(effect_, "blurRadius", 29);
+
+	QAbstractTransition *transition;
+
+	transition = new QEventTransition(this, QEvent::MouseButtonPress);
+	transition->setTargetState(pressed_state_);
+	normal_state_->addTransition(transition);
+
+	transition = new QEventTransition(this, QEvent::MouseButtonDblClick);
+	transition->setTargetState(pressed_state_);
+	normal_state_->addTransition(transition);
+
+	transition = new QEventTransition(this, QEvent::MouseButtonRelease);
+	transition->setTargetState(normal_state_);
+	pressed_state_->addTransition(transition);
+
+	QPropertyAnimation *animation;
+
+	animation = new QPropertyAnimation(effect_, "offset", this);
+	animation->setDuration(100);
+	shadow_state_machine_->addDefaultAnimation(animation);
+
+	animation = new QPropertyAnimation(effect_, "blurRadius", this);
+	animation->setDuration(100);
+	shadow_state_machine_->addDefaultAnimation(animation);
+
+	shadow_state_machine_->setInitialState(normal_state_);
+	shadow_state_machine_->start();
+}
+
+RaisedButton::RaisedButton(QWidget *parent)
+    : FlatButton(parent)
+{
+	init();
+}
+
+RaisedButton::RaisedButton(const QString &text, QWidget *parent)
+    : FlatButton(parent)
+{
+	init();
+	setText(text);
+}
+
+RaisedButton::~RaisedButton()
+{
+}
+
+bool RaisedButton::event(QEvent *event)
+{
+	if (QEvent::EnabledChange == event->type()) {
+		if (isEnabled()) {
+			shadow_state_machine_->start();
+			effect_->setEnabled(true);
+		} else {
+			shadow_state_machine_->stop();
+			effect_->setEnabled(false);
+		}
+	}
+
+	return FlatButton::event(event);
+}
diff --git a/src/ui/Ripple.cc b/src/ui/Ripple.cc
new file mode 100644
index 00000000..107bfd7f
--- /dev/null
+++ b/src/ui/Ripple.cc
@@ -0,0 +1,106 @@
+#include "Ripple.h"
+#include "RippleOverlay.h"
+
+Ripple::Ripple(const QPoint &center, QObject *parent)
+    : QParallelAnimationGroup(parent)
+    , overlay_(0)
+    , radius_anim_(animate("radius"))
+    , opacity_anim_(animate("opacity"))
+    , radius_(0)
+    , opacity_(0)
+    , center_(center)
+{
+	init();
+}
+
+Ripple::Ripple(const QPoint &center, RippleOverlay *overlay, QObject *parent)
+    : QParallelAnimationGroup(parent)
+    , overlay_(overlay)
+    , radius_anim_(animate("radius"))
+    , opacity_anim_(animate("opacity"))
+    , radius_(0)
+    , opacity_(0)
+    , center_(center)
+{
+	init();
+}
+
+Ripple::~Ripple()
+{
+}
+
+void Ripple::setRadius(qreal radius)
+{
+	Q_ASSERT(overlay_);
+
+	if (radius_ == radius)
+		return;
+
+	radius_ = radius;
+	overlay_->update();
+}
+
+void Ripple::setOpacity(qreal opacity)
+{
+	Q_ASSERT(overlay_);
+
+	if (opacity_ == opacity)
+		return;
+
+	opacity_ = opacity;
+	overlay_->update();
+}
+
+void Ripple::setColor(const QColor &color)
+{
+	if (brush_.color() == color)
+		return;
+
+	brush_.setColor(color);
+
+	if (overlay_)
+		overlay_->update();
+}
+
+void Ripple::setBrush(const QBrush &brush)
+{
+	brush_ = brush;
+
+	if (overlay_)
+		overlay_->update();
+}
+
+void Ripple::destroy()
+{
+	Q_ASSERT(overlay_);
+
+	overlay_->removeRipple(this);
+}
+
+QPropertyAnimation *Ripple::animate(const QByteArray &property,
+				    const QEasingCurve &easing,
+				    int duration)
+{
+	QPropertyAnimation *animation = new QPropertyAnimation;
+	animation->setTargetObject(this);
+	animation->setPropertyName(property);
+	animation->setEasingCurve(easing);
+	animation->setDuration(duration);
+
+	addAnimation(animation);
+
+	return animation;
+}
+
+void Ripple::init()
+{
+	setOpacityStartValue(0.5);
+	setOpacityEndValue(0);
+	setRadiusStartValue(0);
+	setRadiusEndValue(300);
+
+	brush_.setColor(Qt::black);
+	brush_.setStyle(Qt::SolidPattern);
+
+	connect(this, SIGNAL(finished()), this, SLOT(destroy()));
+}
diff --git a/src/ui/RippleOverlay.cc b/src/ui/RippleOverlay.cc
new file mode 100644
index 00000000..add030d9
--- /dev/null
+++ b/src/ui/RippleOverlay.cc
@@ -0,0 +1,61 @@
+#include <QPainter>
+
+#include "Ripple.h"
+#include "RippleOverlay.h"
+
+RippleOverlay::RippleOverlay(QWidget *parent)
+    : OverlayWidget(parent)
+    , use_clip_(false)
+{
+	setAttribute(Qt::WA_TransparentForMouseEvents);
+	setAttribute(Qt::WA_NoSystemBackground);
+}
+
+RippleOverlay::~RippleOverlay()
+{
+}
+
+void RippleOverlay::addRipple(Ripple *ripple)
+{
+	ripple->setOverlay(this);
+	ripples_.push_back(ripple);
+	ripple->start();
+}
+
+void RippleOverlay::addRipple(const QPoint &position, qreal radius)
+{
+	Ripple *ripple = new Ripple(position);
+	ripple->setRadiusEndValue(radius);
+	addRipple(ripple);
+}
+
+void RippleOverlay::removeRipple(Ripple *ripple)
+{
+	if (ripples_.removeOne(ripple))
+		delete ripple;
+}
+
+void RippleOverlay::paintEvent(QPaintEvent *event)
+{
+	Q_UNUSED(event)
+
+	QPainter painter(this);
+	painter.setRenderHint(QPainter::Antialiasing);
+	painter.setPen(Qt::NoPen);
+
+	if (use_clip_)
+		painter.setClipPath(clip_path_);
+
+	for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); it++)
+		paintRipple(&painter, *it);
+}
+
+void RippleOverlay::paintRipple(QPainter *painter, Ripple *ripple)
+{
+	const qreal radius = ripple->radius();
+	const QPointF center = ripple->center();
+
+	painter->setOpacity(ripple->opacity());
+	painter->setBrush(ripple->brush());
+	painter->drawEllipse(center, radius, radius);
+}
diff --git a/src/ui/TextField.cc b/src/ui/TextField.cc
new file mode 100644
index 00000000..3b701549
--- /dev/null
+++ b/src/ui/TextField.cc
@@ -0,0 +1,346 @@
+#include "TextField.h"
+
+#include <QApplication>
+#include <QEventTransition>
+#include <QFontDatabase>
+#include <QPaintEvent>
+#include <QPainter>
+#include <QPropertyAnimation>
+
+TextField::TextField(QWidget *parent)
+    : QLineEdit(parent)
+{
+	state_machine_ = new TextFieldStateMachine(this);
+	label_ = 0;
+	label_font_size_ = 9.5;
+	show_label_ = false;
+	background_color_ = QColor("white");
+
+	setFrame(false);
+	setAttribute(Qt::WA_Hover);
+	setMouseTracking(true);
+	setTextMargins(0, 2, 0, 4);
+
+	QFontDatabase db;
+	QFont font(db.font("Open Sans", "Regular", 11));
+	setFont(font);
+
+	state_machine_->start();
+	QCoreApplication::processEvents();
+}
+
+TextField::~TextField()
+{
+}
+
+void TextField::setBackgroundColor(const QColor &color)
+{
+	background_color_ = color;
+}
+
+QColor TextField::backgroundColor() const
+{
+	return background_color_;
+}
+
+void TextField::setShowLabel(bool value)
+{
+	if (show_label_ == value) {
+		return;
+	}
+
+	show_label_ = value;
+
+	if (!label_ && value) {
+		label_ = new TextFieldLabel(this);
+		state_machine_->setLabel(label_);
+	}
+
+	if (value) {
+		setContentsMargins(0, 23, 0, 0);
+	} else {
+		setContentsMargins(0, 0, 0, 0);
+	}
+}
+
+bool TextField::hasLabel() const
+{
+	return show_label_;
+}
+
+void TextField::setLabelFontSize(qreal size)
+{
+	label_font_size_ = size;
+
+	if (label_) {
+		QFont font(label_->font());
+		font.setPointSizeF(size);
+		label_->setFont(font);
+		label_->update();
+	}
+}
+
+qreal TextField::labelFontSize() const
+{
+	return label_font_size_;
+}
+
+void TextField::setLabel(const QString &label)
+{
+	label_text_ = label;
+	setShowLabel(true);
+	label_->update();
+}
+
+QString TextField::label() const
+{
+	return label_text_;
+}
+
+void TextField::setTextColor(const QColor &color)
+{
+	text_color_ = color;
+	setStyleSheet(QString("QLineEdit { color: %1; }").arg(color.name()));
+}
+
+QColor TextField::textColor() const
+{
+	if (!text_color_.isValid()) {
+		return QColor("black");
+	}
+
+	return text_color_;
+}
+
+void TextField::setLabelColor(const QColor &color)
+{
+	label_color_ = color;
+}
+
+QColor TextField::labelColor() const
+{
+	if (!label_color_.isValid()) {
+		return QColor("#abb");  // TODO: Move this into Theme.h
+	}
+
+	return label_color_;
+}
+
+void TextField::setInkColor(const QColor &color)
+{
+	ink_color_ = color;
+}
+
+QColor TextField::inkColor() const
+{
+	if (!ink_color_.isValid()) {
+		return QColor("black");
+	}
+
+	return ink_color_;
+}
+
+void TextField::setUnderlineColor(const QColor &color)
+{
+	underline_color_ = color;
+}
+
+QColor TextField::underlineColor() const
+{
+	if (!underline_color_.isValid()) {
+		return QColor("black");
+	}
+
+	return underline_color_;
+}
+
+bool TextField::event(QEvent *event)
+{
+	switch (event->type()) {
+	case QEvent::Resize:
+	case QEvent::Move: {
+		if (label_)
+			label_->setGeometry(rect());
+		break;
+	}
+	default:
+		break;
+	}
+
+	return QLineEdit::event(event);
+}
+
+void TextField::paintEvent(QPaintEvent *event)
+{
+	QLineEdit::paintEvent(event);
+
+	QPainter painter(this);
+
+	if (text().isEmpty()) {
+		painter.setOpacity(1 - state_machine_->progress());
+		//painter.fillRect(rect(), parentWidget()->palette().color(backgroundRole()));
+		painter.fillRect(rect(), backgroundColor());
+	}
+
+	const int y = height() - 1;
+	const int wd = width() - 5;
+
+	QPen pen;
+	pen.setWidth(1);
+	pen.setColor(underlineColor());
+	painter.setPen(pen);
+	painter.setOpacity(1);
+	painter.drawLine(2.5, y, wd, y);
+
+	QBrush brush;
+	brush.setStyle(Qt::SolidPattern);
+	brush.setColor(inkColor());
+
+	const qreal progress = state_machine_->progress();
+
+	if (progress > 0) {
+		painter.setPen(Qt::NoPen);
+		painter.setBrush(brush);
+		const int w = (1 - progress) * static_cast<qreal>(wd / 2);
+		painter.drawRect(w + 2.5, height() - 2, wd - 2 * w, 2);
+	}
+}
+
+TextFieldStateMachine::TextFieldStateMachine(TextField *parent)
+    : QStateMachine(parent), text_field_(parent)
+{
+	normal_state_ = new QState;
+	focused_state_ = new QState;
+
+	label_ = 0;
+	offset_anim_ = 0;
+	color_anim_ = 0;
+	progress_ = 0.0;
+
+	addState(normal_state_);
+	addState(focused_state_);
+
+	setInitialState(normal_state_);
+
+	QEventTransition *transition;
+	QPropertyAnimation *animation;
+
+	transition = new QEventTransition(parent, QEvent::FocusIn);
+	transition->setTargetState(focused_state_);
+	normal_state_->addTransition(transition);
+
+	animation = new QPropertyAnimation(this, "progress", this);
+	animation->setEasingCurve(QEasingCurve::InCubic);
+	animation->setDuration(310);
+	transition->addAnimation(animation);
+
+	transition = new QEventTransition(parent, QEvent::FocusOut);
+	transition->setTargetState(normal_state_);
+	focused_state_->addTransition(transition);
+
+	animation = new QPropertyAnimation(this, "progress", this);
+	animation->setEasingCurve(QEasingCurve::OutCubic);
+	animation->setDuration(310);
+	transition->addAnimation(animation);
+
+	normal_state_->assignProperty(this, "progress", 0);
+	focused_state_->assignProperty(this, "progress", 1);
+
+	setupProperties();
+
+	connect(text_field_, SIGNAL(textChanged(QString)), this, SLOT(setupProperties()));
+}
+
+TextFieldStateMachine::~TextFieldStateMachine()
+{
+}
+
+void TextFieldStateMachine::setLabel(TextFieldLabel *label)
+{
+	if (label_) {
+		delete label_;
+	}
+
+	if (offset_anim_) {
+		removeDefaultAnimation(offset_anim_);
+		delete offset_anim_;
+	}
+
+	if (color_anim_) {
+		removeDefaultAnimation(color_anim_);
+		delete color_anim_;
+	}
+
+	label_ = label;
+
+	if (label_) {
+		offset_anim_ = new QPropertyAnimation(label_, "offset", this);
+		offset_anim_->setDuration(210);
+		offset_anim_->setEasingCurve(QEasingCurve::OutCubic);
+		addDefaultAnimation(offset_anim_);
+
+		color_anim_ = new QPropertyAnimation(label_, "color", this);
+		color_anim_->setDuration(210);
+		addDefaultAnimation(color_anim_);
+	}
+
+	setupProperties();
+}
+
+void TextFieldStateMachine::setupProperties()
+{
+	if (label_) {
+		const int m = text_field_->textMargins().top();
+
+		if (text_field_->text().isEmpty()) {
+			normal_state_->assignProperty(label_, "offset", QPointF(0, 26));
+		} else {
+			normal_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
+		}
+
+		focused_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
+		focused_state_->assignProperty(label_, "color", text_field_->inkColor());
+		normal_state_->assignProperty(label_, "color", text_field_->labelColor());
+
+		if (0 != label_->offset().y() && !text_field_->text().isEmpty()) {
+			label_->setOffset(QPointF(0, 0 - m));
+		} else if (!text_field_->hasFocus() && label_->offset().y() <= 0 && text_field_->text().isEmpty()) {
+			label_->setOffset(QPointF(0, 26));
+		}
+	}
+
+	text_field_->update();
+}
+
+TextFieldLabel::TextFieldLabel(TextField *parent)
+    : QWidget(parent), text_field_(parent)
+{
+	x_ = 0;
+	y_ = 26;
+	scale_ = 1;
+	color_ = parent->labelColor();
+
+	QFontDatabase db;
+	QFont font(db.font("Open Sans", "Medium", parent->labelFontSize()));
+	font.setLetterSpacing(QFont::PercentageSpacing, 102);
+	setFont(font);
+}
+
+TextFieldLabel::~TextFieldLabel()
+{
+}
+
+void TextFieldLabel::paintEvent(QPaintEvent *)
+{
+	if (!text_field_->hasLabel())
+		return;
+
+	QPainter painter(this);
+	painter.setRenderHint(QPainter::Antialiasing);
+	painter.scale(scale_, scale_);
+	painter.setPen(color_);
+	painter.setOpacity(1);
+
+	QPointF pos(2 + x_, height() - 36 + y_);
+	painter.drawText(pos.x(), pos.y(), text_field_->label());
+}
diff --git a/src/ui/Theme.cc b/src/ui/Theme.cc
new file mode 100644
index 00000000..4c5c19de
--- /dev/null
+++ b/src/ui/Theme.cc
@@ -0,0 +1,73 @@
+#include <QDebug>
+
+#include "Theme.h"
+
+Theme::Theme(QObject *parent)
+    : QObject(parent)
+{
+	setColor("Black", ui::Color::Black);
+
+	setColor("BrightWhite", ui::Color::BrightWhite);
+	setColor("FadedWhite", ui::Color::FadedWhite);
+	setColor("MediumWhite", ui::Color::MediumWhite);
+
+	setColor("BrightGreen", ui::Color::BrightGreen);
+	setColor("DarkGreen", ui::Color::DarkGreen);
+	setColor("LightGreen", ui::Color::LightGreen);
+
+	setColor("Gray", ui::Color::Gray);
+	setColor("Red", ui::Color::Red);
+	setColor("Blue", ui::Color::Blue);
+
+	setColor("Transparent", ui::Color::Transparent);
+}
+
+Theme::~Theme()
+{
+}
+
+QColor Theme::rgba(int r, int g, int b, qreal a) const
+{
+	QColor color(r, g, b);
+	color.setAlphaF(a);
+
+	return color;
+}
+
+QColor Theme::getColor(const QString &key) const
+{
+	if (!colors_.contains(key)) {
+		qWarning() << "Color with key" << key << "could not be found";
+		return QColor();
+	}
+
+	return colors_.value(key);
+}
+
+void Theme::setColor(const QString &key, const QColor &color)
+{
+	colors_.insert(key, color);
+}
+
+void Theme::setColor(const QString &key, ui::Color &color)
+{
+	static const QColor palette[] = {
+		QColor("#171919"),
+
+		QColor("#EBEBEB"),
+		QColor("#C9C9C9"),
+		QColor("#929292"),
+
+		QColor("#1C3133"),
+		QColor("#577275"),
+		QColor("#46A451"),
+
+		QColor("#5D6565"),
+		QColor("#E22826"),
+		QColor("#81B3A9"),
+
+		rgba(0, 0, 0, 0),
+	};
+
+	colors_.insert(key, palette[color]);
+}
diff --git a/src/ui/ThemeManager.cc b/src/ui/ThemeManager.cc
new file mode 100644
index 00000000..3c8a16ab
--- /dev/null
+++ b/src/ui/ThemeManager.cc
@@ -0,0 +1,20 @@
+#include <QFontDatabase>
+
+#include "ThemeManager.h"
+
+ThemeManager::ThemeManager()
+{
+	setTheme(new Theme);
+}
+
+void ThemeManager::setTheme(Theme *theme)
+{
+	theme_ = theme;
+	theme_->setParent(this);
+}
+
+QColor ThemeManager::themeColor(const QString &key) const
+{
+	Q_ASSERT(theme_);
+	return theme_->getColor(key);
+}