Compare commits
No commits in common. "main" and "rewrite" have entirely different histories.
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*.rs]
|
||||
indent_style = tab
|
18
.gitignore
vendored
18
.gitignore
vendored
@ -1,17 +1 @@
|
||||
# ---> Rust
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
target/
|
||||
|
4523
Cargo.lock
generated
Normal file
4523
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
Cargo.toml
33
Cargo.toml
@ -1,32 +1,25 @@
|
||||
[package]
|
||||
name = "retrix"
|
||||
version = "0.1.0"
|
||||
authors = ["Amanda Graven <amanda@amandag.net>"]
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
resolver = "2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
async-stream = "0.3"
|
||||
async-trait = "0.1"
|
||||
dirs-next = "2.0"
|
||||
futures = "0.3"
|
||||
iced = { git = "https://github.com/hecrj/iced", rev = "90fee3a", features = ["debug", "image", "tokio"] }
|
||||
iced_futures = { git = "https://github.com/hecrj/iced", rev = "90fee3a" }
|
||||
#iced = { git = "https://github.com/hecrj/iced", rev = "90fee3a", features = ["debug", "image", "tokio", "glow"] }
|
||||
#iced_glow = { git = "https://github.com/hecrj/iced", rev = "90fee3a", features = ["image"] }
|
||||
directories = "3.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
time = "0.2"
|
||||
tokio = { version = "1.1", features = ["sync"] }
|
||||
toml = "0.5"
|
||||
tracing-subscriber = { version = "0.2", features = ["parking_lot"] }
|
||||
serde_json = "1.0"
|
||||
url = { version = "2.2", features = ["serde"] }
|
||||
|
||||
[dependencies.iced]
|
||||
git = "https://github.com/hecrj/iced"
|
||||
rev = "378a135"
|
||||
features = ["image", "svg", "debug", "tokio"]
|
||||
|
||||
[dependencies.matrix-sdk]
|
||||
git = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rev = "ff68360"
|
||||
default_features = false
|
||||
features = ["encryption", "rustls-tls", "unstable-synapse-quirks", "sled_cryptostore"]
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
rev = "9e1024f"
|
||||
default-features = false
|
||||
features = ["encryption", "sled_state_store", "sled_cryptostore", "rustls-tls", "require_auth_for_profile_requests"]
|
||||
|
625
LICENSE
625
LICENSE
@ -1,625 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright © 2007 Free Software Foundation, Inc. <https://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 <https://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:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
|
||||
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 <https://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 <https://www.gnu.org/
|
||||
licenses /why-not-lgpl.html>.
|
47
README.md
47
README.md
@ -1,47 +0,0 @@
|
||||
# retrix
|
||||
|
||||
Retrix is a lightweight matrix client built with [iced] and [matrix-rust-sdk].
|
||||
|
||||
The project is currently in early stages, and is decidedly not feature complete. Also note that both iced and matrix-sdk are somewhat unstable and under very rapid development, which means that there might be functionality that's broken or can't be implemented that I don't have direct influence over.
|
||||
|
||||
## Features
|
||||
- [x] Rooms
|
||||
- [x] List rooms
|
||||
- [ ] Join rooms
|
||||
- [ ] Explore public room list
|
||||
- [ ] Create room
|
||||
- [ ] Communities
|
||||
- [x] Messages
|
||||
- [x] Plain text
|
||||
- [ ] Formatted text (waiting on iced, markdown will be shown raw)
|
||||
- [ ] Stickers
|
||||
- [x] Images (in unencrypted rooms)
|
||||
- [ ] Audio
|
||||
- [ ] Video
|
||||
- [ ] Location
|
||||
- [x] E2E Encryption
|
||||
- [x] Import key export
|
||||
- [x] Receiving verification start
|
||||
- [ ] Receiving verification request (waiting on matrix-sdk)
|
||||
- [ ] Account settings
|
||||
- [ ] Device management
|
||||
- [ ] Change password
|
||||
- [x] Profile settings
|
||||
- [x] Display name
|
||||
- [ ] Avatar
|
||||
|
||||
### Things I (currently) don't intend to implement
|
||||
- VoIP Calls
|
||||
|
||||
## Building
|
||||
Retrix can be compiled with
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
Be warned that retrix is very heavy to build due to the dependencies it uses. On the less powerful of my laptops, it takes on average 6 minutes to build in release mode.
|
||||
|
||||
## Installing
|
||||
You can put the compiled binary wherever binaries go. Retrix keeps its configuration and caching data in `~/.config/retrix` on linux systems, and in `%APPDATA%\retrix` on windows systems. It will automatically create the needed folder if it does not exist.
|
||||
|
||||
[iced]: https://github.com/hecrj/iced
|
||||
[matrix-rust-sdk]: https://github.com/matrix-org/matrix-rust-sdk
|
7
rustfmt.toml
Normal file
7
rustfmt.toml
Normal file
@ -0,0 +1,7 @@
|
||||
edition = "2018"
|
||||
hard_tabs=true
|
||||
max_width = 100
|
||||
wrap_comments = true
|
||||
imports_granularity = "Crate"
|
||||
use_small_heuristics = "Max"
|
||||
group_imports = "StdExternalCrate"
|
145
src/config.rs
Normal file
145
src/config.rs
Normal file
@ -0,0 +1,145 @@
|
||||
//! Configuration
|
||||
|
||||
use std::{fmt::Display, fs::File, io::ErrorKind as IoErrorKind};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Result alias for this module
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Data for a valid login, including access token and homeserver address
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Session {
|
||||
/// Access token, mxid and device id of the session
|
||||
pub session: matrix_sdk::Session,
|
||||
/// The address of the homeserver
|
||||
pub homeserver: String,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Read session data from disk.
|
||||
pub fn from_file() -> Result<Option<Session>> {
|
||||
let dirs = dirs()?;
|
||||
let file = File::open(dirs.data_dir().join("session.json"));
|
||||
let file = match file {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
return match e.kind() {
|
||||
IoErrorKind::NotFound => Ok(None),
|
||||
_ => Err(e.into()),
|
||||
}
|
||||
}
|
||||
};
|
||||
let session = serde_json::from_reader(file)?;
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
/// Writes the session data to disk.
|
||||
pub fn write(&self) -> Result<()> {
|
||||
let dirs = dirs()?;
|
||||
std::fs::create_dir_all(dirs.data_dir())?;
|
||||
let file = File::create(dirs.data_dir().join("session.json"))?;
|
||||
serde_json::to_writer_pretty(file, self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the application
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// The global zoom level.
|
||||
pub zoom: f64,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Creates a new `Config` with default settings
|
||||
pub fn new() -> Config {
|
||||
Config { zoom: 1.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Reads the configuration from disk, or creates a new file with default settings if none
|
||||
/// exists
|
||||
pub fn from_file() -> Result<Config> {
|
||||
let dirs = dirs()?;
|
||||
let file = File::open(dirs.config_dir().join("retrix.json"));
|
||||
// Create the file if it doesn't exist
|
||||
let file = match file {
|
||||
Ok(file) => file,
|
||||
Err(e) => match e.kind() {
|
||||
IoErrorKind::NotFound => {
|
||||
let config = Config::default();
|
||||
config.write()?;
|
||||
return Ok(config);
|
||||
}
|
||||
_ => return Err(e.into()),
|
||||
},
|
||||
};
|
||||
let config: Config = serde_json::from_reader(file)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Writes the configuration to disk
|
||||
pub fn write(&self) -> Result<()> {
|
||||
let dirs = dirs()?;
|
||||
std::fs::create_dir_all(dirs.config_dir())?;
|
||||
let file = File::create(dirs.config_dir().join("retrix.json"))?;
|
||||
serde_json::to_writer_pretty(file, self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates structure holding paths for config dirs etc.
|
||||
pub fn dirs() -> Result<dirs::ProjectDirs> {
|
||||
dirs::ProjectDirs::from("net.amandag", "", "retrix").ok_or(Error::HomeDir)
|
||||
}
|
||||
|
||||
/// Errors which can happen when handling config files
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// An error happened opening or creating a file
|
||||
Io(std::io::Error),
|
||||
/// (De)serializing a file failed
|
||||
Parsing(serde_json::Error),
|
||||
/// The home directory couldn't be found
|
||||
HomeDir,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::Io(e) => write!(f, "Error opening file: {}", e),
|
||||
Error::Parsing(e) => write!(f, "Error in config file: {}", e),
|
||||
Error::HomeDir => write!(f, "Couldn't find home directory"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
Error::Io(ref e) => Some(e),
|
||||
Error::Parsing(ref e) => Some(e),
|
||||
Error::HomeDir => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Error::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(e: serde_json::Error) -> Self {
|
||||
Error::Parsing(e)
|
||||
}
|
||||
}
|
41
src/main.rs
41
src/main.rs
@ -1,25 +1,30 @@
|
||||
extern crate dirs_next as dirs;
|
||||
//! Retrix is a matrix client
|
||||
#![warn(
|
||||
missing_docs,
|
||||
missing_debug_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_extern_crates,
|
||||
unused_allocation
|
||||
)]
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::{fs::Permissions, os::unix::fs::PermissionsExt};
|
||||
use config::Config;
|
||||
use iced::{Application, Settings};
|
||||
use ui::Flags;
|
||||
|
||||
use iced::Application;
|
||||
use crate::{config::Session, ui::Retrix};
|
||||
|
||||
extern crate directories as dirs;
|
||||
|
||||
pub mod config;
|
||||
pub mod matrix;
|
||||
pub mod style;
|
||||
pub mod ui;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let config_dir = dirs::config_dir().unwrap().join("retrix");
|
||||
// Make sure config dir exists and is not accessible by other users.
|
||||
if !config_dir.is_dir() {
|
||||
std::fs::create_dir(&config_dir)?;
|
||||
#[cfg(unix)]
|
||||
std::fs::set_permissions(&config_dir, Permissions::from_mode(0o700))?;
|
||||
}
|
||||
|
||||
ui::Retrix::run(iced::Settings::default())?;
|
||||
|
||||
Ok(())
|
||||
fn main() {
|
||||
let session = Session::from_file().unwrap();
|
||||
let config = Config::from_file().unwrap();
|
||||
let settings =
|
||||
Settings { text_multithreading: true, ..Settings::with_flags(Flags { config, session }) };
|
||||
Retrix::run(settings).unwrap();
|
||||
}
|
||||
|
373
src/matrix.rs
373
src/matrix.rs
@ -1,331 +1,90 @@
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use async_stream::stream;
|
||||
use matrix_sdk::{
|
||||
api::r0::{account::register::Request as RegistrationRequest, uiaa::AuthData},
|
||||
events::{
|
||||
room::message::{MessageEvent, MessageEventContent, MessageType},
|
||||
AnyMessageEvent, AnyRoomEvent, AnySyncRoomEvent, AnyToDeviceEvent,
|
||||
},
|
||||
identifiers::{DeviceId, EventId, RoomId, ServerName, UserId},
|
||||
reqwest::Url,
|
||||
Client, ClientConfig, LoopCtrl, SyncSettings,
|
||||
config::ClientConfig,
|
||||
reqwest::Url,
|
||||
ruma::{DeviceIdBox, UserId},
|
||||
Client,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type Error = anyhow::Error;
|
||||
|
||||
// Needed to be able to serialize `Session`s. Should be done with serde remote.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Session {
|
||||
access_token: String,
|
||||
pub user_id: UserId,
|
||||
pub device_id: Box<DeviceId>,
|
||||
pub homeserver: String,
|
||||
/// The homeserver URL
|
||||
pub homeserver: Url,
|
||||
/// Access token for authentication
|
||||
pub access_token: String,
|
||||
/// The user's mxid.
|
||||
pub user_id: UserId,
|
||||
/// The user's device ID.
|
||||
pub device_id: DeviceIdBox,
|
||||
}
|
||||
|
||||
impl From<Session> for matrix_sdk::Session {
|
||||
fn from(s: Session) -> Self {
|
||||
Self {
|
||||
access_token: s.access_token,
|
||||
user_id: s.user_id,
|
||||
device_id: s.device_id,
|
||||
}
|
||||
}
|
||||
/// Create a matrix client and log it in to the server at the given URL with the
|
||||
/// given credentials.
|
||||
pub async fn login(url: &str, user: &str, password: &str) -> Result<Client, LoginError> {
|
||||
let url: Url =
|
||||
if !url.contains("://") { format!("https://{}", url).parse() } else { url.parse() }?;
|
||||
let client = Client::new_with_config(url.clone(), config()?)?;
|
||||
let response = client.login(user, password, None, None).await?;
|
||||
let session = Session {
|
||||
homeserver: url,
|
||||
access_token: response.access_token,
|
||||
user_id: response.user_id,
|
||||
device_id: response.device_id,
|
||||
};
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub async fn signup(
|
||||
username: &str,
|
||||
password: &str,
|
||||
server: &str,
|
||||
device_name: Option<&str>,
|
||||
) -> Result<(Client, Session), Error> {
|
||||
let url = Url::parse(server)?;
|
||||
let client = client(url)?;
|
||||
|
||||
let mut request = RegistrationRequest::new();
|
||||
request.username = Some(username);
|
||||
request.password = Some(password);
|
||||
request.initial_device_display_name = Some(device_name.unwrap_or("retrix"));
|
||||
request.inhibit_login = false;
|
||||
|
||||
// Get UIAA session key
|
||||
let uiaa = match client.register(request.clone()).await {
|
||||
Err(e) => match e.uiaa_response().cloned() {
|
||||
Some(uiaa) => uiaa,
|
||||
None => return Err(anyhow::anyhow!("Missing UIAA response")),
|
||||
},
|
||||
Ok(_) => {
|
||||
return Err(anyhow::anyhow!("Missing UIAA response"));
|
||||
}
|
||||
};
|
||||
// Get the first step in the authentication flow (we're ignoring the rest)
|
||||
let stages = uiaa.flows.get(0);
|
||||
let kind = stages.and_then(|flow| flow.stages.get(0)).cloned();
|
||||
|
||||
// Set authentication data, fallback to password type
|
||||
request.auth = Some(AuthData::DirectRequest {
|
||||
kind: kind.as_deref().unwrap_or("m.login.password"),
|
||||
session: uiaa.session.as_deref(),
|
||||
auth_parameters: Default::default(),
|
||||
});
|
||||
|
||||
let response = client.register(request).await?;
|
||||
client.sync_once(SyncSettings::new()).await?;
|
||||
|
||||
let session = Session {
|
||||
access_token: response.access_token.unwrap(),
|
||||
user_id: response.user_id,
|
||||
device_id: response.device_id.unwrap(),
|
||||
homeserver: server.to_owned(),
|
||||
};
|
||||
|
||||
Ok((client, session))
|
||||
/// Errors that can happen when logging in
|
||||
#[derive(Debug)]
|
||||
pub enum LoginError {
|
||||
/// Invalid URL
|
||||
Url(url::ParseError),
|
||||
/// Matrix SDK error
|
||||
Sdk(matrix_sdk::Error),
|
||||
/// I/O error
|
||||
Io(std::io::Error),
|
||||
}
|
||||
|
||||
/// Login with credentials, creating a new authentication session
|
||||
pub async fn login(
|
||||
username: &str,
|
||||
password: &str,
|
||||
server: &str,
|
||||
device_name: Option<&str>,
|
||||
) -> Result<(Client, Session), Error> {
|
||||
let url = Url::parse(server)?;
|
||||
let client = client(url)?;
|
||||
|
||||
let response = client
|
||||
.login(
|
||||
username,
|
||||
password,
|
||||
None,
|
||||
Some(device_name.unwrap_or("retrix")),
|
||||
)
|
||||
.await?;
|
||||
let session = Session {
|
||||
access_token: response.access_token,
|
||||
user_id: response.user_id,
|
||||
device_id: response.device_id,
|
||||
homeserver: server.to_owned(),
|
||||
};
|
||||
write_session(&session)?;
|
||||
client.sync_once(SyncSettings::new()).await?;
|
||||
|
||||
Ok((client, session))
|
||||
impl std::fmt::Display for LoginError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LoginError::Url(_) => write!(f, "Invalid homeserver address"),
|
||||
LoginError::Sdk(e) => write!(f, "{}", e),
|
||||
LoginError::Io(e) => write!(f, "Filesystem error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn restore_login(session: Session) -> Result<(Client, Session), Error> {
|
||||
let url = Url::parse(&session.homeserver)?;
|
||||
let client = client(url)?;
|
||||
impl std::error::Error for LoginError {}
|
||||
|
||||
client.restore_login(session.clone().into()).await?;
|
||||
client.sync_once(SyncSettings::new()).await?;
|
||||
|
||||
Ok((client, session))
|
||||
impl From<url::ParseError> for LoginError {
|
||||
fn from(e: url::ParseError) -> Self {
|
||||
LoginError::Url(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a matrix client handler with the desired configuration
|
||||
fn client(url: Url) -> Result<Client, matrix_sdk::Error> {
|
||||
let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix"));
|
||||
Client::new_with_config(url, config)
|
||||
impl From<matrix_sdk::Error> for LoginError {
|
||||
fn from(e: matrix_sdk::Error) -> Self {
|
||||
LoginError::Sdk(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// File path to store session data in
|
||||
fn session_path() -> std::path::PathBuf {
|
||||
dirs::config_dir()
|
||||
.unwrap()
|
||||
.join("retrix")
|
||||
.join("session.toml")
|
||||
impl From<std::io::Error> for LoginError {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
LoginError::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read session data from config file
|
||||
pub fn get_session() -> Result<Option<Session>, Error> {
|
||||
let path = session_path();
|
||||
if !path.is_file() {
|
||||
return Ok(None);
|
||||
}
|
||||
let session: Session = toml::from_slice(&std::fs::read(path)?)?;
|
||||
Ok(Some(session))
|
||||
/// Configuration for `Clients`.
|
||||
fn config() -> Result<ClientConfig, std::io::Error> {
|
||||
Ok(ClientConfig::new().store_path(&path()?))
|
||||
}
|
||||
|
||||
/// Save session data to config file
|
||||
fn write_session(session: &Session) -> Result<(), Error> {
|
||||
let serialized = toml::to_string(&session)?;
|
||||
std::fs::write(session_path(), serialized)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Break down an mxc url to its authority and path
|
||||
pub fn parse_mxc(url: &str) -> Result<(Box<ServerName>, String), Error> {
|
||||
let url = Url::parse(&url)?;
|
||||
anyhow::ensure!(url.scheme() == "mxc", "Not an mxc url");
|
||||
let host = url.host_str().ok_or_else(|| anyhow::anyhow!("url"))?;
|
||||
let server_name: Box<ServerName> = <&ServerName>::try_from(host)?.into();
|
||||
let path = url.path_segments().and_then(|mut p| p.next());
|
||||
match path {
|
||||
Some(path) => Ok((server_name, path.to_owned())),
|
||||
_ => Err(anyhow::anyhow!("Invalid mxc url")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes an iced subscription for listening for matrix events.
|
||||
pub struct MatrixSync {
|
||||
client: matrix_sdk::Client,
|
||||
join: Option<tokio::task::JoinHandle<()>>,
|
||||
//id: String,
|
||||
}
|
||||
|
||||
impl MatrixSync {
|
||||
pub fn subscription(client: matrix_sdk::Client) -> iced::Subscription<Event> {
|
||||
iced::Subscription::from_recipe(MatrixSync { client, join: None })
|
||||
}
|
||||
}
|
||||
|
||||
/// A matrix event that should be passed to the iced subscription
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
/// An event for an invited room
|
||||
Invited(AnyRoomEvent, Arc<matrix_sdk::room::Invited>),
|
||||
/// An event for a joined room
|
||||
Joined(AnyRoomEvent, Arc<matrix_sdk::room::Joined>),
|
||||
/// An event for a left room
|
||||
Left(AnyRoomEvent, Arc<matrix_sdk::room::Left>),
|
||||
/// A to-device event
|
||||
ToDevice(AnyToDeviceEvent),
|
||||
/// Synchronization token
|
||||
Token(String),
|
||||
}
|
||||
|
||||
impl<H, I> iced_futures::subscription::Recipe<H, I> for MatrixSync
|
||||
where
|
||||
H: std::hash::Hasher,
|
||||
{
|
||||
type Output = Event;
|
||||
|
||||
fn hash(&self, state: &mut H) {
|
||||
use std::hash::Hash;
|
||||
|
||||
std::any::TypeId::of::<Self>().hash(state);
|
||||
//self.id.hash(state);
|
||||
}
|
||||
|
||||
fn stream(
|
||||
mut self: Box<Self>,
|
||||
_input: iced_futures::BoxStream<I>,
|
||||
) -> iced_futures::BoxStream<Self::Output> {
|
||||
let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
|
||||
let client = self.client.clone();
|
||||
let join = tokio::task::spawn(async move {
|
||||
client
|
||||
.sync_with_callback(
|
||||
SyncSettings::new()
|
||||
.token(client.sync_token().await.unwrap())
|
||||
.timeout(Duration::from_secs(30)),
|
||||
|response| async {
|
||||
for (id, room) in response.rooms.join {
|
||||
let joined = Arc::new(client.get_joined_room(&id).unwrap());
|
||||
for event in room.state.events {
|
||||
let id = id.clone();
|
||||
let event = AnyRoomEvent::State(event.into_full_event(id));
|
||||
sender.send(Event::Joined(event, Arc::clone(&joined))).ok();
|
||||
}
|
||||
for event in room.timeline.events {
|
||||
let event = event.into_full_event(id.clone());
|
||||
sender.send(Event::Joined(event, Arc::clone(&joined))).ok();
|
||||
}
|
||||
}
|
||||
for event in response.to_device.events {
|
||||
sender.send(Event::ToDevice(event)).ok();
|
||||
}
|
||||
LoopCtrl::Continue
|
||||
},
|
||||
)
|
||||
.await;
|
||||
});
|
||||
self.join = Some(join);
|
||||
let stream = stream! {
|
||||
while let Some(item) = receiver.recv().await {
|
||||
yield item;
|
||||
}
|
||||
};
|
||||
Box::pin(stream)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnyRoomEventExt {
|
||||
/// Gets the event id of the underlying event
|
||||
fn event_id(&self) -> &EventId;
|
||||
/// Gets the ´origin_server_ts` member of the underlying event
|
||||
fn origin_server_ts(&self) -> SystemTime;
|
||||
/// Gets the mxc url in a message event if there is noe
|
||||
fn image_url(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl AnyRoomEventExt for AnyRoomEvent {
|
||||
fn event_id(&self) -> &EventId {
|
||||
match self {
|
||||
AnyRoomEvent::Message(e) => e.event_id(),
|
||||
AnyRoomEvent::State(e) => e.event_id(),
|
||||
AnyRoomEvent::RedactedMessage(e) => e.event_id(),
|
||||
AnyRoomEvent::RedactedState(e) => e.event_id(),
|
||||
}
|
||||
}
|
||||
fn origin_server_ts(&self) -> SystemTime {
|
||||
match self {
|
||||
AnyRoomEvent::Message(e) => e.origin_server_ts(),
|
||||
AnyRoomEvent::State(e) => e.origin_server_ts(),
|
||||
AnyRoomEvent::RedactedMessage(e) => e.origin_server_ts(),
|
||||
AnyRoomEvent::RedactedState(e) => e.origin_server_ts(),
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
fn image_url(&self) -> Option<String> {
|
||||
match self {
|
||||
AnyRoomEvent::Message(message) => message.image_url(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnyMessageEventExt {
|
||||
fn image_url(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl AnyMessageEventExt for AnyMessageEvent {
|
||||
fn image_url(&self) -> Option<String> {
|
||||
match self {
|
||||
AnyMessageEvent::RoomMessage(MessageEvent {
|
||||
content:
|
||||
MessageEventContent {
|
||||
msgtype: MessageType::Image(ref image),
|
||||
..
|
||||
},
|
||||
..
|
||||
}) => image.url.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnySyncRoomEventExt {
|
||||
fn into_full_event(self, room_id: RoomId) -> AnyRoomEvent;
|
||||
}
|
||||
|
||||
impl AnySyncRoomEventExt for AnySyncRoomEvent {
|
||||
fn into_full_event(self, id: RoomId) -> AnyRoomEvent {
|
||||
match self {
|
||||
AnySyncRoomEvent::Message(e) => AnyRoomEvent::Message(e.into_full_event(id)),
|
||||
AnySyncRoomEvent::State(e) => AnyRoomEvent::State(e.into_full_event(id)),
|
||||
AnySyncRoomEvent::RedactedMessage(e) => {
|
||||
AnyRoomEvent::RedactedMessage(e.into_full_event(id))
|
||||
}
|
||||
AnySyncRoomEvent::RedactedState(e) => {
|
||||
AnyRoomEvent::RedactedState(e.into_full_event(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The path the the sdk store should be put in.
|
||||
fn path() -> Result<PathBuf, std::io::Error> {
|
||||
let path = Path::new(&std::env::var_os("HOME").unwrap()).join(".config").join("retrix");
|
||||
std::fs::create_dir_all(&path)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
24
src/style.rs
Normal file
24
src/style.rs
Normal file
@ -0,0 +1,24 @@
|
||||
//! Style definitions for various elements
|
||||
|
||||
/// Style definitions for [`iced::Container`]
|
||||
pub mod container {
|
||||
use iced::{
|
||||
container::{Style, StyleSheet},
|
||||
Color,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Style for a container displaying an error message
|
||||
pub struct Error;
|
||||
|
||||
impl StyleSheet for Error {
|
||||
fn style(&self) -> Style {
|
||||
iced::container::Style {
|
||||
background: Color::from_rgb(1.0, 0.0, 0.0).into(),
|
||||
text_color: Some(Color::from_rgb(1.0, 1.0, 1.0)),
|
||||
border_radius: 2.0,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
170
src/ui/login.rs
Normal file
170
src/ui/login.rs
Normal file
@ -0,0 +1,170 @@
|
||||
//! The login prompt
|
||||
|
||||
use iced::{
|
||||
widget::{button::State as ButtonState, text_input::State as InputState},
|
||||
Button, Column, Command, Container, Element, Length, Space, Text, TextInput, Toggler,
|
||||
};
|
||||
|
||||
use self::Message::*;
|
||||
use crate::{matrix, style};
|
||||
|
||||
/// Data for the login prompt
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Login {
|
||||
/// The username
|
||||
user: String,
|
||||
/// The password
|
||||
password: String,
|
||||
/// Whether the password should be visible.
|
||||
show_password: bool,
|
||||
/// The homeserver URL.
|
||||
homeserver: String,
|
||||
/// Whether we're waiting for a response to a login attempt
|
||||
waiting: bool,
|
||||
/// The text of an error message
|
||||
error: Option<String>,
|
||||
|
||||
/// Widget state
|
||||
state: State,
|
||||
}
|
||||
|
||||
/// Login prompt widget state
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
/// State for the login input
|
||||
user: InputState,
|
||||
/// State from the password input
|
||||
password: InputState,
|
||||
/// State for the homeserver input
|
||||
homeserver: InputState,
|
||||
/// State for the login button
|
||||
login: ButtonState,
|
||||
}
|
||||
|
||||
/// Notification to change the state for the login view.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
/// The login input changed.
|
||||
InputUser(String),
|
||||
/// The password input changed.
|
||||
InputPassword(String),
|
||||
/// The homerserver input changed.
|
||||
InputHomeserver(String),
|
||||
/// The "show password" toggle has been switched.
|
||||
TogglePassword(bool),
|
||||
/// Triggers login.
|
||||
Login,
|
||||
/// A login attempt failed.
|
||||
LoginFailed(String),
|
||||
/// Login completed.
|
||||
LoggedIn(matrix_sdk::Client),
|
||||
/// Hide the error message.
|
||||
ResetError,
|
||||
}
|
||||
|
||||
impl Login {
|
||||
/// Update state
|
||||
pub fn update(&mut self, message: Message) -> Command<super::Message> {
|
||||
match message {
|
||||
InputUser(input) => self.user = input,
|
||||
InputPassword(input) => self.password = input,
|
||||
InputHomeserver(input) => self.homeserver = input,
|
||||
TogglePassword(toggle) => self.show_password = toggle,
|
||||
Login => {
|
||||
self.waiting = true;
|
||||
let homeserver = self.homeserver.clone();
|
||||
let user = self.user.clone();
|
||||
let password = self.password.clone();
|
||||
let command = async move {
|
||||
let client = match matrix::login(&homeserver, &user, &password).await {
|
||||
Ok(client) => client,
|
||||
Err(e) => return LoginFailed(e.to_string()),
|
||||
};
|
||||
LoggedIn(client)
|
||||
};
|
||||
return Command::perform(command, super::Message::from);
|
||||
}
|
||||
LoginFailed(error) => {
|
||||
self.waiting = false;
|
||||
self.error = Some(error);
|
||||
}
|
||||
LoggedIn(_) => (),
|
||||
ResetError => self.error = None,
|
||||
};
|
||||
Command::none()
|
||||
}
|
||||
|
||||
/// Generate widgets for this view
|
||||
pub fn view(&mut self) -> Element<super::Message> {
|
||||
let error_message: iced::Element<_> = match self.error {
|
||||
Some(ref error) => Container::new(Text::new(error.clone()))
|
||||
.center_x()
|
||||
.width(Length::Fill)
|
||||
.padding(5)
|
||||
.style(style::container::Error)
|
||||
.into(),
|
||||
None => Space::new(0.into(), 0.into()).into(),
|
||||
};
|
||||
let user_input =
|
||||
TextInput::new(&mut self.state.user, "alice", &self.user, |i| InputUser(i).into())
|
||||
.padding(5);
|
||||
|
||||
let mut password_input =
|
||||
TextInput::new(&mut self.state.password, "verysecret", &self.password, |i| {
|
||||
InputPassword(i).into()
|
||||
})
|
||||
.padding(5);
|
||||
if !self.show_password {
|
||||
password_input = password_input.password();
|
||||
}
|
||||
|
||||
let password_toggle =
|
||||
Toggler::new(self.show_password, String::from("show password"), |b| {
|
||||
TogglePassword(b).into()
|
||||
})
|
||||
.text_size(15);
|
||||
|
||||
let homeserver_input = TextInput::new(
|
||||
&mut self.state.homeserver,
|
||||
"https://matrix.org",
|
||||
&self.homeserver,
|
||||
|i| InputHomeserver(i).into(),
|
||||
)
|
||||
.padding(5);
|
||||
|
||||
let login_button = Button::new(
|
||||
&mut self.state.login,
|
||||
Text::new("Log in")
|
||||
.horizontal_alignment(iced::alignment::Horizontal::Center)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.on_press(Message::Login.into())
|
||||
.width(Length::Fill);
|
||||
|
||||
let mut column = Column::new()
|
||||
.width(500.into())
|
||||
.push(error_message)
|
||||
.push(Text::new("User name"))
|
||||
.push(user_input)
|
||||
.push(Space::with_height(10.into()))
|
||||
.push(Text::new("Password"))
|
||||
.push(password_input)
|
||||
.push(password_toggle)
|
||||
.push(Space::with_height(5.into()))
|
||||
.push(Text::new("Homeserver address"))
|
||||
.push(homeserver_input)
|
||||
.push(login_button);
|
||||
|
||||
if self.waiting {
|
||||
column = column.push(Text::new("Logging in"));
|
||||
}
|
||||
|
||||
Container::new(column).center_x().center_y().width(Length::Fill).height(Length::Fill).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for super::Message {
|
||||
fn from(message: Message) -> Self {
|
||||
super::Message::Login(message)
|
||||
}
|
||||
}
|
141
src/ui/prompt.rs
141
src/ui/prompt.rs
@ -1,141 +0,0 @@
|
||||
//! Login prompt
|
||||
|
||||
use iced::{text_input, Button, Column, Container, Element, Radio, Row, Text, TextInput};
|
||||
|
||||
use crate::ui::Message;
|
||||
|
||||
/// View for the login prompt
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PromptView {
|
||||
/// Username input field
|
||||
pub user_input: text_input::State,
|
||||
/// Password input field
|
||||
pub password_input: text_input::State,
|
||||
/// Homeserver input field
|
||||
pub server_input: text_input::State,
|
||||
/// Device name input field
|
||||
pub device_input: text_input::State,
|
||||
/// Button to trigger login
|
||||
pub login_button: iced::button::State,
|
||||
|
||||
/// Username
|
||||
pub user: String,
|
||||
/// Password
|
||||
pub password: String,
|
||||
/// Homeserver
|
||||
pub server: String,
|
||||
/// Device name to create login session under
|
||||
pub device_name: String,
|
||||
/// Whether to log in or sign up
|
||||
pub action: PromptAction,
|
||||
/// Error message
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl PromptView {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn view(&mut self) -> Element<Message> {
|
||||
let mut content = Column::new()
|
||||
.width(500.into())
|
||||
.spacing(5)
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(15)
|
||||
.push(Radio::new(
|
||||
PromptAction::Login,
|
||||
"Login",
|
||||
Some(self.action),
|
||||
Message::SetAction,
|
||||
))
|
||||
.push(Radio::new(
|
||||
PromptAction::Signup,
|
||||
"Sign up",
|
||||
Some(self.action),
|
||||
Message::SetAction,
|
||||
)),
|
||||
)
|
||||
.push(
|
||||
Column::new().push(Text::new("Username")).push(
|
||||
TextInput::new(
|
||||
&mut self.user_input,
|
||||
"Username",
|
||||
&self.user,
|
||||
Message::SetUser,
|
||||
)
|
||||
.padding(5),
|
||||
),
|
||||
)
|
||||
.push(
|
||||
Column::new().push(Text::new("Password")).push(
|
||||
TextInput::new(
|
||||
&mut self.password_input,
|
||||
"Password",
|
||||
&self.password,
|
||||
Message::SetPassword,
|
||||
)
|
||||
.password()
|
||||
.padding(5),
|
||||
),
|
||||
)
|
||||
.push(
|
||||
Column::new().push(Text::new("Homeserver")).push(
|
||||
TextInput::new(
|
||||
&mut self.server_input,
|
||||
"https://homeserver.com",
|
||||
&self.server,
|
||||
Message::SetServer,
|
||||
)
|
||||
.padding(5),
|
||||
),
|
||||
)
|
||||
.push(
|
||||
Column::new().push(Text::new("Device name")).push(
|
||||
TextInput::new(
|
||||
&mut self.device_input,
|
||||
"retrix on my laptop",
|
||||
&self.device_name,
|
||||
Message::SetDeviceName,
|
||||
)
|
||||
.padding(5),
|
||||
),
|
||||
);
|
||||
let button = match self.action {
|
||||
PromptAction::Login => {
|
||||
Button::new(&mut self.login_button, Text::new("Login")).on_press(Message::Login)
|
||||
}
|
||||
PromptAction::Signup => {
|
||||
content = content.push(
|
||||
Text::new("NB: Signup is very naively implemented, and prone to breaking")
|
||||
.color([1.0, 0.5, 0.0]),
|
||||
);
|
||||
Button::new(&mut self.login_button, Text::new("Sign up")).on_press(Message::Signup)
|
||||
}
|
||||
};
|
||||
content = content.push(button);
|
||||
if let Some(ref error) = self.error {
|
||||
content = content.push(Text::new(error).color([1.0, 0.0, 0.0]));
|
||||
}
|
||||
|
||||
Container::new(content)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.width(iced::Length::Fill)
|
||||
.height(iced::Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PromptAction {
|
||||
Login,
|
||||
Signup,
|
||||
}
|
||||
|
||||
impl Default for PromptAction {
|
||||
fn default() -> Self {
|
||||
PromptAction::Login
|
||||
}
|
||||
}
|
53
src/ui/session.rs
Normal file
53
src/ui/session.rs
Normal file
@ -0,0 +1,53 @@
|
||||
//! View for a logged in session
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use iced::{Element, Row, Space};
|
||||
use matrix_sdk::{ruma::RoomId, Client};
|
||||
|
||||
/// The main view, for a logged in session
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct View {
|
||||
client: matrix_sdk::Client,
|
||||
|
||||
/// List of known rooms
|
||||
room_list: RoomList,
|
||||
|
||||
/// Widget state
|
||||
state: State,
|
||||
}
|
||||
|
||||
/// Widget state.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct State {}
|
||||
|
||||
/// The list of rooms
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct RoomList {
|
||||
names: HashMap<RoomId, String>,
|
||||
}
|
||||
|
||||
impl RoomList {
|
||||
fn view(&mut self) -> Element<super::Message> {
|
||||
Space::new(0.into(), 0.into()).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// State change notification
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
/// The name for a room has been recalculated.
|
||||
RoomName(RoomId, String),
|
||||
}
|
||||
|
||||
impl View {
|
||||
/// Create a new view.
|
||||
pub fn with_client(client: Client) -> Self {
|
||||
Self { client, room_list: RoomList::default(), state: State::default() }
|
||||
}
|
||||
|
||||
/// Generate widgets
|
||||
pub fn view(&mut self) -> Element<super::Message> {
|
||||
Row::new().push(self.room_list.view()).into()
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
//! Settings view.
|
||||
|
||||
use iced::{Button, Column, Container, Element, Length, Radio, Row, Text, TextInput};
|
||||
|
||||
use super::{Message, RoomSorting};
|
||||
|
||||
/// Settings menu
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct SettingsView {
|
||||
/// Display name to set
|
||||
pub display_name: String,
|
||||
/// Are we saving the display name?
|
||||
pub saving_name: bool,
|
||||
|
||||
/// Display name text input
|
||||
pub display_name_input: iced::text_input::State,
|
||||
/// Button to set display name
|
||||
pub display_name_button: iced::button::State,
|
||||
|
||||
/// Path to import encryption keys from
|
||||
pub key_path: String,
|
||||
/// Password to decrypt the keys with
|
||||
pub key_password: String,
|
||||
|
||||
/// Encryption key path entry
|
||||
pub key_path_input: iced::text_input::State,
|
||||
/// Entry for key password
|
||||
pub key_password_input: iced::text_input::State,
|
||||
/// Button to import keys
|
||||
pub key_import_button: iced::button::State,
|
||||
/// Button to close settings view
|
||||
pub close_button: iced::button::State,
|
||||
}
|
||||
|
||||
impl SettingsView {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn view(&mut self, sort: RoomSorting) -> Element<Message> {
|
||||
let content = Column::new()
|
||||
.width(500.into())
|
||||
.spacing(5)
|
||||
.push(Text::new("Profile").size(25))
|
||||
.push(
|
||||
Column::new().push(Text::new("Display name")).push(
|
||||
Row::new()
|
||||
.push(
|
||||
TextInput::new(
|
||||
&mut self.display_name_input,
|
||||
"Alice",
|
||||
&self.display_name,
|
||||
Message::SetDisplayNameInput,
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.padding(5),
|
||||
)
|
||||
.push(match self.saving_name {
|
||||
false => Button::new(&mut self.display_name_button, Text::new("Save"))
|
||||
.on_press(Message::SaveDisplayName),
|
||||
true => {
|
||||
Button::new(&mut self.display_name_button, Text::new("Saving..."))
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.push(Text::new("Appearance").size(25))
|
||||
.push(Text::new("Sort messages by:"))
|
||||
.push(Radio::new(
|
||||
RoomSorting::Alphabetic,
|
||||
"Name",
|
||||
Some(sort),
|
||||
Message::SetSort,
|
||||
))
|
||||
.push(Radio::new(
|
||||
RoomSorting::Recent,
|
||||
"Activity",
|
||||
Some(sort),
|
||||
Message::SetSort,
|
||||
))
|
||||
.push(Text::new("Encryption").size(25))
|
||||
.push(
|
||||
Column::new()
|
||||
.push(Text::new("Import key (enter path)"))
|
||||
.push(
|
||||
TextInput::new(
|
||||
&mut self.key_path_input,
|
||||
"/home/user/exported_keys.txt",
|
||||
&self.key_path,
|
||||
Message::SetKeyPath,
|
||||
)
|
||||
.padding(5),
|
||||
),
|
||||
)
|
||||
.push(
|
||||
Column::new().push(Text::new("Key password")).push(
|
||||
TextInput::new(
|
||||
&mut self.key_password_input,
|
||||
"SecretPassword42",
|
||||
&self.key_password,
|
||||
Message::SetKeyPassword,
|
||||
)
|
||||
.password()
|
||||
.padding(5),
|
||||
),
|
||||
)
|
||||
.push(
|
||||
Button::new(&mut self.key_import_button, Text::new("Import keys"))
|
||||
.on_press(Message::ImportKeys),
|
||||
)
|
||||
.push(
|
||||
Row::new().width(Length::Fill).push(
|
||||
Button::new(&mut self.close_button, Text::new("Close"))
|
||||
.on_press(Message::CloseSettings),
|
||||
),
|
||||
);
|
||||
Container::new(content)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
//! Theming for widgets.
|
||||
|
||||
/// Which colorscheme to use
|
||||
pub enum Theme {
|
||||
Default,
|
||||
}
|
Loading…
Reference in New Issue
Block a user