@ -0,0 +1,12 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
/app/release
|
||||
/TODO.txt
|
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WizardSettings">
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="imageWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="imageAssetPanel">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="launcher">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="foregroundImage">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="scalingPercent" value="50" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="backgroundAssetType" value="COLOR" />
|
||||
<entry key="backgroundColor" value="006db3" />
|
||||
<entry key="foregroundImage" value="$PROJECT_DIR$/app/src/main/res/drawable-xxxhdpi/baseline_email_white_48.png" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="vectorWizard">
|
||||
<value>
|
||||
<PersistentState />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,29 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<Objective-C-extensions>
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
</code_scheme>
|
||||
</component>
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="5">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,27 @@
|
||||
Safe email
|
||||
==========
|
||||
|
||||
Frequently Asked Questions
|
||||
--------------------------
|
||||
|
||||
<a name="FAQ1"></a>
|
||||
**(1) Which email providers are supported?**
|
||||
|
||||
* Gmail
|
||||
* Outlook
|
||||
|
||||
<a name="FAQ2"></a>
|
||||
**(2) What is a valid security certificate?**
|
||||
|
||||
Valid security certificates are officially signed (not self signed) and have matching a host name.
|
||||
|
||||
<a name="FAQ3"></a>
|
||||
**(3) Which permissions are needed and why?**
|
||||
|
||||
* Full network access (INTERNET): to send and receive email
|
||||
* View network connections (ACCESS_NETWORK_STATE): to monitor internet connectivity changes
|
||||
* Run at startup (RECEIVE_BOOT_COMPLETED): to start monitoring on device start
|
||||
|
||||
<br>
|
||||
|
||||
If you have another question, you can use [this forum](https://forum.xda-developers.com/).
|
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 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>.
|
@ -0,0 +1,117 @@
|
||||
Safe email
|
||||
==========
|
||||
|
||||
Simple, secure and efficient email app
|
||||
|
||||
This email app might be for you if your current email app:
|
||||
|
||||
* takes long to show messages
|
||||
* can manage only one mailbox
|
||||
* cannot show related messages
|
||||
* cannot work offline
|
||||
* looks outdated
|
||||
* is closed source, potentially violating your privacy
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* 100 % open source
|
||||
* Multiple accounts (inboxes)
|
||||
* Multiple identities (outboxes)
|
||||
* Unified inbox
|
||||
* Message threading
|
||||
* Two way synchronization
|
||||
* Offline storage and operations
|
||||
* Material design
|
||||
* Dark theme
|
||||
|
||||
Simple
|
||||
------
|
||||
|
||||
* Easy navigation
|
||||
* No settings
|
||||
* No bells and whistles
|
||||
|
||||
Secure
|
||||
------
|
||||
|
||||
* Connect to email servers only
|
||||
* Allow encrypted connections only
|
||||
* Accept valid security certificates only
|
||||
* [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) authentication required
|
||||
* Text view only (converted [HTML](https://en.wikipedia.org/wiki/HTML))
|
||||
* No special permissions required
|
||||
* No advertisements
|
||||
* No analytics and no tracking
|
||||
|
||||
Efficient
|
||||
---------
|
||||
|
||||
* No [POP](https://en.wikipedia.org/wiki/Post_Office_Protocol) support
|
||||
* [IMAP IDLE](https://en.wikipedia.org/wiki/IMAP_IDLE) required
|
||||
* Android 6 Marshmallow or later required
|
||||
|
||||
This app starts a foreground service with a low priority status bar notification to make sure you'll never miss new email.
|
||||
|
||||
Frequently Asked Questions
|
||||
--------------------------
|
||||
|
||||
See [here](https://github.com/M66B/SafeEmail/blob/master/FAQ.md) for a list of often asked questions.
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
* For support on Safe email, please go [here](https://forum.xda-developers.com/)
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
*Documentation*
|
||||
|
||||
Contributions to this document and the frequently asked questions
|
||||
are preferred in the form of [pull requests](https://help.github.com/articles/creating-a-pull-request/).
|
||||
|
||||
*Translations*
|
||||
|
||||
* You can translate the in-app texts of Safe email [here](https://crowdin.com/project/safe-email/)
|
||||
* If your language is not listed, please send a message through [this contact form](https://contact.faircode.eu/)
|
||||
|
||||
*Source code*
|
||||
|
||||
Building Safe email from source code is straightforward with [Android Studio](http://developer.android.com/sdk/).
|
||||
It is expected that you can solve build problems yourself, so there is no support on building.
|
||||
|
||||
Source code contributions are preferred in the form of [pull requests](https://help.github.com/articles/creating-a-pull-request/).
|
||||
Please [contact me](https://contact.faircode.eu/) first to tell me what your plans are.
|
||||
|
||||
Please note that you agree to the license below by contributing, including the copyright.
|
||||
|
||||
Attribution
|
||||
-----------
|
||||
|
||||
Safe email uses:
|
||||
|
||||
* [JavaMail](https://javaee.github.io/javamail/). Copyright (c) 1997-2018 Oracle® and/or its affiliates. All rights reserved. [GPLv2+CE license](https://javaee.github.io/javamail/JavaMail-License).
|
||||
* [jsoup](https://jsoup.org/). Copyright © 2009 - 2017 Jonathan Hedley. [MIT license](https://jsoup.org/license).
|
||||
* [Android Support Library](https://developer.android.com/tools/support-library/). Copyright (C) 2011 The Android Open Source Project. [Apache license](https://android.googlesource.com/platform/frameworks/support/+/master/LICENSE.txt).
|
||||
* [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/). Copyright 2018 The Android Open Source Project, Inc. [Apache license](https://github.com/googlesamples/android-architecture-components/blob/master/LICENSE).
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
[GNU General Public License version 3](https://www.gnu.org/licenses/gpl.txt)
|
||||
|
||||
Copyright (c) 2018 Marcel Bokhorst. All rights reserved
|
||||
|
||||
Safe email 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.
|
||||
|
||||
Safe email 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 Safe email. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
|
@ -0,0 +1 @@
|
||||
/build
|
@ -0,0 +1,60 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
applicationId "eu.faircode.email"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "0.1"
|
||||
archivesBaseName = "SafeEmail-v$versionName"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled = true
|
||||
useProguard = true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled = true
|
||||
useProguard = true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst 'META-INF/LICENSE.txt'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://maven.java.net/content/groups/public/"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
// https://developer.android.com/topic/libraries/support-library/revisions.html
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0-alpha1'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.+'
|
||||
implementation 'com.android.support:recyclerview-v7:28.0.0-alpha1'
|
||||
implementation 'com.android.support:design:28.0.0-alpha1'
|
||||
|
||||
// https://developer.android.com/topic/libraries/architecture/adding-components.html
|
||||
implementation "android.arch.lifecycle:extensions:1.1.+"
|
||||
implementation "android.arch.persistence.room:runtime:1.1.+"
|
||||
annotationProcessor "android.arch.lifecycle:compiler:1.1.+"
|
||||
annotationProcessor "android.arch.persistence.room:compiler:1.1.+"
|
||||
|
||||
// https://javaee.github.io/javamail/
|
||||
implementation 'com.sun.mail:android-mail:1.6.0'
|
||||
implementation 'com.sun.mail:android-activation:1.6.0'
|
||||
|
||||
// https://jsoup.org/
|
||||
implementation 'org.jsoup:jsoup:1.11.3'
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you after the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
-keep class eu.faircode.email.**
|
||||
-keepnames class eu.faircode.email.** { *; }
|
||||
|
||||
#JavaMail
|
||||
-dontshrink
|
||||
-keep class javax.** {*;}
|
||||
-keep class com.sun.** {*;}
|
||||
-keep class myjava.** {*;}
|
||||
-keep class org.apache.harmony.** {*;}
|
||||
-dontwarn java.awt.**
|
||||
-dontwarn java.beans.Beans
|
||||
-dontwarn javax.activation.**
|
||||
-dontwarn javax.security.**
|
||||
|
||||
#jsoup
|
||||
-keeppackagenames org.jsoup.nodes
|
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.faircode.email">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<!-- uses-permission android:name="android.permission.READ_CONTACTS" /-->
|
||||
|
||||
<application
|
||||
android:name=".ApplicationEx"
|
||||
android:allowBackup="false"
|
||||
android:appCategory="productivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppThemeLight">
|
||||
|
||||
<activity android:name=".ActivitySetup" />
|
||||
|
||||
<activity android:name=".ActivityView">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ActivityCompose" />
|
||||
|
||||
<service android:name=".ServiceSynchronize" />
|
||||
|
||||
<receiver android:name=".ReceiverAutostart">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,35 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
abstract class ActivityBase extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String theme = prefs.getString("theme", "light");
|
||||
setTheme("dark".equals(theme) ? R.style.AppThemeDark : R.style.AppThemeLight);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
|
||||
public class ActivityCompose extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
|
||||
static final int LOADER_COMPOSE_GET = 1;
|
||||
static final int LOADER_COMPOSE_PUT = 2;
|
||||
static final int LOADER_COMPOSE_DELETE = 3;
|
||||
|
||||
static final int REQUEST_CONTACT = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_compose);
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
|
||||
if (getSupportFragmentManager().getFragments().size() == 0) {
|
||||
FragmentCompose fragment = new FragmentCompose();
|
||||
Bundle args = getIntent().getExtras();
|
||||
if (args == null)
|
||||
args = new Bundle();
|
||||
fragment.setArguments(args);
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("compose");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
finish();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
|
||||
public class ActivitySetup extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_setup);
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
|
||||
if (getSupportFragmentManager().getFragments().size() == 0) {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentSetup()).addToBackStack("setup");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
finish();
|
||||
}
|
||||
}
|
@ -0,0 +1,388 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ActivityView extends ActivityBase implements FragmentManager.OnBackStackChangedListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private DrawerLayout drawerLayout;
|
||||
private ListView drawerList;
|
||||
private ActionBarDrawerToggle drawerToggle;
|
||||
|
||||
static final int LOADER_ACCOUNT_PUT = 1;
|
||||
static final int LOADER_IDENTITY_PUT = 2;
|
||||
static final int LOADER_FOLDER_PUT = 3;
|
||||
|
||||
static final int REQUEST_VIEW = 1;
|
||||
|
||||
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
|
||||
static final String ACTION_VIEW_MESSAGE = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGE";
|
||||
static final String ACTION_EDIT_FOLDER = BuildConfig.APPLICATION_ID + ".EDIT_FOLDER";
|
||||
static final String ACTION_EDIT_ACCOUNT = BuildConfig.APPLICATION_ID + ".EDIT_ACCOUNT";
|
||||
static final String ACTION_EDIT_IDENTITY = BuildConfig.APPLICATION_ID + ".EDIT_IDENTITY";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_view);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
|
||||
drawerLayout = findViewById(R.id.drawer_layout);
|
||||
drawerLayout.setScrimColor(Helper.resolveColor(this, R.attr.colorDrawerScrim));
|
||||
|
||||
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.app_name, R.string.app_name) {
|
||||
public void onDrawerClosed(View view) {
|
||||
super.onDrawerClosed(view);
|
||||
getSupportActionBar().setTitle(getString(R.string.app_name));
|
||||
}
|
||||
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
super.onDrawerOpened(drawerView);
|
||||
getSupportActionBar().setTitle(getString(R.string.app_name));
|
||||
}
|
||||
};
|
||||
drawerLayout.addDrawerListener(drawerToggle);
|
||||
|
||||
drawerList = findViewById(R.id.drawer_list);
|
||||
drawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
DrawerItem item = (DrawerItem) parent.getAdapter().getItem(position);
|
||||
switch (item.getId()) {
|
||||
case R.string.menu_unified:
|
||||
onMenuUnified();
|
||||
break;
|
||||
case R.string.menu_folders:
|
||||
onMenuFolders();
|
||||
break;
|
||||
case R.string.menu_accounts:
|
||||
onMenuAccounts();
|
||||
break;
|
||||
case R.string.menu_identities:
|
||||
onMenuIdentities();
|
||||
break;
|
||||
case R.string.menu_theme:
|
||||
onMenuTheme();
|
||||
break;
|
||||
case R.string.menu_setup:
|
||||
onMenuSetup();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!item.isCheckable())
|
||||
drawerLayout.closeDrawer(drawerList);
|
||||
}
|
||||
});
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
|
||||
updateDrawer();
|
||||
|
||||
if (getSupportFragmentManager().getFragments().size() == 0)
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
drawerToggle.syncState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
IntentFilter iff = new IntentFilter();
|
||||
iff.addAction(ACTION_VIEW_MESSAGES);
|
||||
iff.addAction(ACTION_VIEW_MESSAGE);
|
||||
iff.addAction(ACTION_EDIT_FOLDER);
|
||||
iff.addAction(ACTION_EDIT_ACCOUNT);
|
||||
iff.addAction(ACTION_EDIT_IDENTITY);
|
||||
lbm.registerReceiver(receiver, iff);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
lbm.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
drawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (drawerLayout.isDrawerOpen(drawerList))
|
||||
drawerLayout.closeDrawer(drawerList);
|
||||
else
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
if ("eula".equals(key))
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (drawerToggle.onOptionsItemSelected(item))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (prefs.getBoolean("eula", false)) {
|
||||
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
|
||||
|
||||
FragmentMessages fragment = new FragmentMessages();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("folder", -1);
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("unified");
|
||||
fragmentTransaction.commit();
|
||||
|
||||
Fragment eula = getSupportFragmentManager().findFragmentByTag("eula");
|
||||
if (eula != null)
|
||||
getSupportFragmentManager().beginTransaction().remove(eula).commit();
|
||||
|
||||
DB.getInstance(this).account().liveAccounts(true).observe(this, new Observer<List<EntityAccount>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAccount> accounts) {
|
||||
if (accounts.size() == 0)
|
||||
startActivity(new Intent(ActivityView.this, ActivitySetup.class));
|
||||
else
|
||||
ServiceSynchronize.start(ActivityView.this);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentEula(), "eula");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDrawer() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
ArrayAdapterDrawer drawerArray = new ArrayAdapterDrawer(this, R.layout.item_drawer);
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_unified));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_folders));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_accounts));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_identities));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_theme, "dark".equals(prefs.getString("theme", "light"))));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_setup));
|
||||
drawerList.setAdapter(drawerArray);
|
||||
}
|
||||
|
||||
private void onMenuUnified() {
|
||||
FragmentMessages fragment = new FragmentMessages();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("folder", -1);
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("unified");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuFolders() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentFolders()).addToBackStack("folders");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuAccounts() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentAccounts()).addToBackStack("accounts");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuIdentities() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentIdentities()).addToBackStack("identities");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuTheme() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String theme = prefs.getString("theme", "light");
|
||||
theme = ("dark".equals(theme) ? "light" : "dark");
|
||||
prefs.edit().putString("theme", theme).apply();
|
||||
recreate();
|
||||
}
|
||||
|
||||
private void onMenuSetup() {
|
||||
startActivity(new Intent(ActivityView.this, ActivitySetup.class));
|
||||
}
|
||||
|
||||
private class DrawerItem {
|
||||
private int id;
|
||||
private String title;
|
||||
private boolean checkable;
|
||||
private boolean checked;
|
||||
|
||||
DrawerItem(Context context, int title) {
|
||||
this.id = title;
|
||||
this.title = context.getString(title);
|
||||
this.checkable = false;
|
||||
this.checked = false;
|
||||
}
|
||||
|
||||
DrawerItem(Context context, int title, boolean checked) {
|
||||
this.id = title;
|
||||
this.title = context.getString(title);
|
||||
this.checkable = true;
|
||||
this.checked = checked;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
public boolean isCheckable() {
|
||||
return this.checkable;
|
||||
}
|
||||
|
||||
public boolean isChecked() {
|
||||
return this.checked;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ArrayAdapterDrawer extends ArrayAdapter<DrawerItem> {
|
||||
private int resource;
|
||||
|
||||
ArrayAdapterDrawer(@NonNull Context context, int resource) {
|
||||
super(context, resource);
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
|
||||
View row;
|
||||
if (null == convertView)
|
||||
row = LayoutInflater.from(getContext()).inflate(this.resource, null);
|
||||
else
|
||||
row = convertView;
|
||||
|
||||
DrawerItem item = getItem(position);
|
||||
|
||||
TextView tv = row.findViewById(R.id.tvItem);
|
||||
CheckBox cb = row.findViewById(R.id.cbItem);
|
||||
tv.setText(item.getTitle());
|
||||
cb.setVisibility(item.isCheckable() ? View.VISIBLE : View.GONE);
|
||||
cb.setChecked(item.isChecked());
|
||||
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (ACTION_VIEW_MESSAGES.equals(intent.getAction())) {
|
||||
FragmentMessages fragment = new FragmentMessages();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("messages");
|
||||
fragmentTransaction.commit();
|
||||
} else if (ACTION_VIEW_MESSAGE.equals(intent.getAction())) {
|
||||
FragmentMessage fragment = new FragmentMessage();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("message");
|
||||
fragmentTransaction.commit();
|
||||
} else if (ACTION_EDIT_FOLDER.equals(intent.getAction())) {
|
||||
FragmentFolder fragment = new FragmentFolder();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("folder");
|
||||
fragmentTransaction.commit();
|
||||
} else if (ACTION_EDIT_ACCOUNT.equals(intent.getAction())) {
|
||||
FragmentAccount fragment = new FragmentAccount();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
|
||||
fragmentTransaction.commit();
|
||||
} else if (ACTION_EDIT_IDENTITY.equals(intent.getAction())) {
|
||||
FragmentIdentity fragment = new FragmentIdentity();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.util.ListUpdateCallback;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<EntityAccount> all = new ArrayList<>();
|
||||
private List<EntityAccount> filtered = new ArrayList<>();
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
View itemView;
|
||||
ImageView ivPrimary;
|
||||
TextView tvName;
|
||||
ImageView ivSync;
|
||||
TextView tvHost;
|
||||
TextView tvUser;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
ivPrimary = itemView.findViewById(R.id.ivPrimary);
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
ivSync = itemView.findViewById(R.id.ivSync);
|
||||
tvHost = itemView.findViewById(R.id.tvHost);
|
||||
tvUser = itemView.findViewById(R.id.tvUser);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
itemView.setOnClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
EntityAccount account = filtered.get(getLayoutPosition());
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_EDIT_ACCOUNT)
|
||||
.putExtra("id", account.id));
|
||||
}
|
||||
}
|
||||
|
||||
AdapterAccount(Context context) {
|
||||
this.context = context;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(List<EntityAccount> accounts) {
|
||||
Log.i(Helper.TAG, "Set accounts=" + accounts.size());
|
||||
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
Collections.sort(accounts, new Comparator<EntityAccount>() {
|
||||
@Override
|
||||
public int compare(EntityAccount a1, EntityAccount a2) {
|
||||
return collator.compare(a1.host, a2.host);
|
||||
}
|
||||
});
|
||||
|
||||
all.clear();
|
||||
all.addAll(accounts);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(AdapterAccount.this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<EntityAccount> prev;
|
||||
private List<EntityAccount> next;
|
||||
|
||||
MessageDiffCallback(List<EntityAccount> prev, List<EntityAccount> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityAccount f1 = prev.get(oldItemPosition);
|
||||
EntityAccount f2 = next.get(newItemPosition);
|
||||
return f1.id.equals(f2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityAccount f1 = prev.get(oldItemPosition);
|
||||
EntityAccount f2 = next.get(newItemPosition);
|
||||
return f1.equals(f2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_account, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
|
||||
EntityAccount account = filtered.get(position);
|
||||
|
||||
holder.ivPrimary.setVisibility(account.primary ? View.VISIBLE : View.GONE);
|
||||
holder.tvName.setText(account.name);
|
||||
holder.ivSync.setVisibility(account.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.tvHost.setText(String.format("%s:%d", account.host, account.port));
|
||||
holder.tvUser.setText(account.user);
|
||||
|
||||
holder.wire();
|
||||
}
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.util.ListUpdateCallback;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<TupleFolderEx> all = new ArrayList<>();
|
||||
private List<TupleFolderEx> filtered = new ArrayList<>();
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder
|
||||
implements View.OnClickListener, View.OnLongClickListener {
|
||||
View itemView;
|
||||
TextView tvName;
|
||||
TextView tvAfter;
|
||||
ImageView ivSync;
|
||||
TextView tvCount;
|
||||
TextView tvType;
|
||||
TextView tvAccount;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
tvAfter = itemView.findViewById(R.id.tvAfter);
|
||||
ivSync = itemView.findViewById(R.id.ivSync);
|
||||
tvCount = itemView.findViewById(R.id.tvCount);
|
||||
tvType = itemView.findViewById(R.id.tvType);
|
||||
tvAccount = itemView.findViewById(R.id.tvAccount);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
itemView.setOnClickListener(this);
|
||||
itemView.setOnLongClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
itemView.setOnClickListener(null);
|
||||
itemView.setOnLongClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
TupleFolderEx folder = filtered.get(getLayoutPosition());
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_VIEW_MESSAGES)
|
||||
.putExtra("folder", folder.id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
TupleFolderEx folder = filtered.get(getLayoutPosition());
|
||||
|
||||
if (!EntityFolder.TYPE_OUTBOX.equals(folder.type)) {
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_EDIT_FOLDER)
|
||||
.putExtra("id", folder.id));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AdapterFolder(Context context) {
|
||||
this.context = context;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(List<TupleFolderEx> folders) {
|
||||
Log.i(Helper.TAG, "Set folders=" + folders.size());
|
||||
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
Collections.sort(folders, new Comparator<TupleFolderEx>() {
|
||||
@Override
|
||||
public int compare(TupleFolderEx f1, TupleFolderEx f2) {
|
||||
if (f1.accountName == null)
|
||||
if (f2.accountName == null)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
else if (f2.accountName == null)
|
||||
return 1;
|
||||
else
|
||||
return collator.compare(f1.accountName, f2.accountName);
|
||||
}
|
||||
});
|
||||
|
||||
all.clear();
|
||||
all.addAll(folders);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(AdapterFolder.this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<TupleFolderEx> prev;
|
||||
private List<TupleFolderEx> next;
|
||||
|
||||
MessageDiffCallback(List<TupleFolderEx> prev, List<TupleFolderEx> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
TupleFolderEx f1 = prev.get(oldItemPosition);
|
||||
TupleFolderEx f2 = next.get(newItemPosition);
|
||||
return f1.id.equals(f2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
TupleFolderEx f1 = prev.get(oldItemPosition);
|
||||
TupleFolderEx f2 = next.get(newItemPosition);
|
||||
return f1.equals(f2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_folder, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
|
||||
TupleFolderEx folder = filtered.get(position);
|
||||
|
||||
String name = Helper.localizeFolderName(context, folder.name);
|
||||
if (folder.unseen > 0)
|
||||
holder.tvName.setText(context.getString(R.string.title_folder_unseen, name, folder.unseen));
|
||||
else
|
||||
holder.tvName.setText(name);
|
||||
|
||||
holder.tvName.setTypeface(null, folder.unseen > 0 ? Typeface.BOLD : Typeface.NORMAL);
|
||||
holder.tvAfter.setText(Integer.toString(folder.after));
|
||||
holder.tvAfter.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.ivSync.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.tvCount.setText(Integer.toString(folder.messages));
|
||||
holder.tvType.setText(folder.type);
|
||||
holder.tvAccount.setText(folder.accountName);
|
||||
|
||||
holder.wire();
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.util.ListUpdateCallback;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<EntityIdentity> all = new ArrayList<>();
|
||||
private List<EntityIdentity> filtered = new ArrayList<>();
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
View itemView;
|
||||
ImageView ivPrimary;
|
||||
TextView tvName;
|
||||
ImageView ivSync;
|
||||
TextView tvHost;
|
||||
TextView tvUser;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
ivPrimary = itemView.findViewById(R.id.ivPrimary);
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
ivSync = itemView.findViewById(R.id.ivSync);
|
||||
tvHost = itemView.findViewById(R.id.tvHost);
|
||||
tvUser = itemView.findViewById(R.id.tvUser);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
itemView.setOnClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
EntityIdentity identity = filtered.get(getLayoutPosition());
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_EDIT_IDENTITY)
|
||||
.putExtra("id", identity.id));
|
||||
}
|
||||
}
|
||||
|
||||
AdapterIdentity(Context context) {
|
||||
this.context = context;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(List<EntityIdentity> identities) {
|
||||
Log.i(Helper.TAG, "Set identities=" + identities.size());
|
||||
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
Collections.sort(identities, new Comparator<EntityIdentity>() {
|
||||
@Override
|
||||
public int compare(EntityIdentity i1, EntityIdentity i2) {
|
||||
return collator.compare(i1.host, i2.host);
|
||||
}
|
||||
});
|
||||
|
||||
all.clear();
|
||||
all.addAll(identities);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(AdapterIdentity.this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<EntityIdentity> prev;
|
||||
private List<EntityIdentity> next;
|
||||
|
||||
MessageDiffCallback(List<EntityIdentity> prev, List<EntityIdentity> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityIdentity i1 = prev.get(oldItemPosition);
|
||||
EntityIdentity i2 = next.get(newItemPosition);
|
||||
return i1.id.equals(i2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityIdentity i1 = prev.get(oldItemPosition);
|
||||
EntityIdentity i2 = next.get(newItemPosition);
|
||||
return i1.equals(i2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_identity, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
|
||||
EntityIdentity identity = filtered.get(position);
|
||||
|
||||
holder.ivPrimary.setVisibility(identity.primary ? View.VISIBLE : View.GONE);
|
||||
holder.tvName.setText(identity.name);
|
||||
holder.ivSync.setVisibility(identity.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.tvHost.setText(String.format("%s:%d", identity.host, identity.port));
|
||||
holder.tvUser.setText(identity.user);
|
||||
|
||||
holder.wire();
|
||||
}
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.util.ListUpdateCallback;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<TupleMessageEx> all = new ArrayList<>();
|
||||
private List<TupleMessageEx> filtered = new ArrayList<>();
|
||||
|
||||
private ExecutorService executor = Executors.newCachedThreadPool();
|
||||
|
||||
enum ViewType {FOLDER, THREAD}
|
||||
|
||||
private ViewType viewType;
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder
|
||||
implements View.OnClickListener {
|
||||
View itemView;
|
||||
TextView tvAddress;
|
||||
TextView tvTime;
|
||||
TextView tvSubject;
|
||||
TextView tvCount;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
tvAddress = itemView.findViewById(R.id.tvAddress);
|
||||
tvTime = itemView.findViewById(R.id.tvTime);
|
||||
tvSubject = itemView.findViewById(R.id.tvSubject);
|
||||
tvCount = itemView.findViewById(R.id.tvCount);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
itemView.setOnClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final TupleMessageEx message = filtered.get(getLayoutPosition());
|
||||
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (EntityFolder.TYPE_DRAFTS.equals(message.folderType))
|
||||
context.startActivity(
|
||||
new Intent(context, ActivityCompose.class)
|
||||
.putExtra("id", message.id));
|
||||
else {
|
||||
if (!message.seen && !message.ui_seen) {
|
||||
message.ui_seen = !message.ui_seen;
|
||||
DB.getInstance(context).message().updateMessage(message);
|
||||
EntityOperation.queue(context, message, EntityOperation.SEEN, message.ui_seen);
|
||||
}
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_VIEW_MESSAGE)
|
||||
.putExtra("folder", message.folder)
|
||||
.putExtra("id", message.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AdapterMessage(Context context, ViewType viewType) {
|
||||
this.context = context;
|
||||
this.viewType = viewType;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(List<TupleMessageEx> messages) {
|
||||
Log.i(Helper.TAG, "Set messages=" + messages.size());
|
||||
|
||||
Collections.sort(messages, new Comparator<TupleMessageEx>() {
|
||||
@Override
|
||||
public int compare(TupleMessageEx m1, TupleMessageEx m2) {
|
||||
if (EntityFolder.isOutgoing(m1.folderType))
|
||||
return -Long.compare(m1.received, m2.received);
|
||||
else
|
||||
return -Long.compare(m1.sent, m2.sent);
|
||||
}
|
||||
});
|
||||
|
||||
all.clear();
|
||||
all.addAll(messages);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(AdapterMessage.this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<TupleMessageEx> prev;
|
||||
private List<TupleMessageEx> next;
|
||||
|
||||
MessageDiffCallback(List<TupleMessageEx> prev, List<TupleMessageEx> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
TupleMessageEx m1 = prev.get(oldItemPosition);
|
||||
TupleMessageEx m2 = next.get(newItemPosition);
|
||||
return m1.id.equals(m2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
TupleMessageEx m1 = prev.get(oldItemPosition);
|
||||
TupleMessageEx m2 = next.get(newItemPosition);
|
||||
return m1.equals(m2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_message, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
|
||||
TupleMessageEx message = filtered.get(position);
|
||||
|
||||
if (EntityFolder.isOutgoing(message.folderType)) {
|
||||
holder.tvAddress.setText(message.to == null ? null : MessageHelper.getFormattedAddresses(message.to));
|
||||
holder.tvTime.setText(DateUtils.getRelativeTimeSpanString(context, message.received));
|
||||
} else {
|
||||
holder.tvAddress.setText(message.from == null ? null : MessageHelper.getFormattedAddresses(message.from));
|
||||
holder.tvTime.setText(message.sent == null ? null : DateUtils.getRelativeTimeSpanString(context, message.sent));
|
||||
}
|
||||
|
||||
holder.tvSubject.setText(message.subject);
|
||||
if (viewType == ViewType.FOLDER) {
|
||||
holder.tvCount.setText(Integer.toString(message.count));
|
||||
holder.tvCount.setVisibility(message.count > 1 ? View.VISIBLE : View.GONE);
|
||||
} else
|
||||
holder.tvCount.setText(Helper.localizeFolderName(context, message.folderName));
|
||||
|
||||
boolean unseen = (message.thread == null ? !message.seen : message.unseen > 0);
|
||||
int visibility = (unseen ? Typeface.BOLD : Typeface.NORMAL);
|
||||
holder.tvAddress.setTypeface(null, visibility);
|
||||
holder.tvTime.setTypeface(null, visibility);
|
||||
holder.tvSubject.setTypeface(null, visibility);
|
||||
holder.tvCount.setTypeface(null, visibility);
|
||||
|
||||
holder.wire();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class ApplicationEx extends Application {
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.room.Database;
|
||||
import android.arch.persistence.room.Room;
|
||||
import android.arch.persistence.room.RoomDatabase;
|
||||
import android.arch.persistence.room.TypeConverter;
|
||||
import android.arch.persistence.room.TypeConverters;
|
||||
import android.arch.persistence.room.migration.Migration;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
EntityFolder.class,
|
||||
EntityMessage.class,
|
||||
EntityOperation.class
|
||||
},
|
||||
version = 1,
|
||||
exportSchema = true
|
||||
)
|
||||
|
||||
@TypeConverters({DB.Converters.class})
|
||||
public abstract class DB extends RoomDatabase {
|
||||
public abstract DaoIdentity identity();
|
||||
|
||||
public abstract DaoAccount account();
|
||||
|
||||
public abstract DaoFolder folder();
|
||||
|
||||
public abstract DaoMessage message();
|
||||
|
||||
public abstract DaoOperation operation();
|
||||
|
||||
private static DB sInstance;
|
||||
|
||||
private static final String DB_NAME = "email.db";
|
||||
|
||||
public static synchronized DB getInstance(Context context) {
|
||||
if (sInstance == null)
|
||||
sInstance = migrate(Room.databaseBuilder(context.getApplicationContext(), DB.class, DB_NAME));
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private static DB migrate(RoomDatabase.Builder<DB> builder) {
|
||||
return builder
|
||||
//.addMigrations(MIGRATION_1_2)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE message ADD COLUMN error TEXT");
|
||||
}
|
||||
};
|
||||
|
||||
public static class Converters {
|
||||
@TypeConverter
|
||||
public static byte[] fromString(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String fromBytes(byte[] value) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoAccount {
|
||||
@Query("SELECT * FROM account WHERE synchronize = :synchronize")
|
||||
List<EntityAccount> getAccounts(boolean synchronize);
|
||||
|
||||
@Query("SELECT * FROM account")
|
||||
LiveData<List<EntityAccount>> liveAccounts();
|
||||
|
||||
@Query("SELECT * FROM account WHERE synchronize = :synchronize")
|
||||
LiveData<List<EntityAccount>> liveAccounts(boolean synchronize);
|
||||
|
||||
@Query("SELECT * FROM account WHERE id = :id")
|
||||
EntityAccount getAccount(long id);
|
||||
|
||||
@Query("SELECT * FROM account WHERE id = :id")
|
||||
LiveData<EntityAccount> liveAccount(long id);
|
||||
|
||||
@Query("SELECT * FROM account ORDER BY id LIMIT 1")
|
||||
LiveData<EntityAccount> liveFirstAccount();
|
||||
|
||||
@Query("SELECT" +
|
||||
" (SELECT COUNT(*) FROM account WHERE synchronize) AS accounts," +
|
||||
" (SELECT COUNT(*) FROM operation JOIN message ON message.id = operation.message JOIN account ON account.id = message.account WHERE synchronize) AS operations")
|
||||
LiveData<TupleAccountStats> liveStats();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertAccount(EntityAccount account);
|
||||
|
||||
@Update
|
||||
void updateAccount(EntityAccount account);
|
||||
|
||||
@Query("UPDATE account SET `primary` = 0")
|
||||
void resetPrimary();
|
||||
}
|
||||
|
@ -0,0 +1,90 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoFolder {
|
||||
@Query("SELECT * FROM folder WHERE account = :account AND synchronize = :synchronize")
|
||||
List<EntityFolder> getFolders(long account, boolean synchronize);
|
||||
|
||||
@Query("SELECT * FROM folder WHERE account = :account AND type = '" + EntityFolder.TYPE_USER + "'")
|
||||
List<EntityFolder> getUserFolders(long account);
|
||||
|
||||
@Query("SELECT folder.*, account.name AS accountName" +
|
||||
", COUNT(message.id) AS messages" +
|
||||
", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" +
|
||||
" FROM folder" +
|
||||
" LEFT JOIN account ON account.id = folder.account" +
|
||||
" LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide" +
|
||||
" GROUP BY folder.id")
|
||||
LiveData<List<TupleFolderEx>> liveFolders();
|
||||
|
||||
@Query("SELECT folder.* FROM folder WHERE folder.id = :id")
|
||||
LiveData<EntityFolder> liveFolder(long id);
|
||||
|
||||
@Query("SELECT folder.*, account.name AS accountName" +
|
||||
", COUNT(message.id) AS messages" +
|
||||
", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" +
|
||||
" FROM folder" +
|
||||
" LEFT JOIN account ON account.id = folder.account" +
|
||||
" LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide" +
|
||||
" WHERE folder.id = :id")
|
||||
LiveData<TupleFolderEx> liveFolderEx(long id);
|
||||
|
||||
@Query("SELECT * FROM folder WHERE id = :id")
|
||||
EntityFolder getFolder(Long id);
|
||||
|
||||
@Query("SELECT * FROM folder WHERE account = :account AND name = :name")
|
||||
EntityFolder getFolder(Long account, String name);
|
||||
|
||||
@Query("SELECT folder.* FROM folder" +
|
||||
" JOIN account ON account.id = folder.account" +
|
||||
" WHERE account.`primary` AND type = '" + EntityFolder.TYPE_DRAFTS + "' ")
|
||||
EntityFolder getPrimaryDraftFolder();
|
||||
|
||||
@Query("SELECT folder.* FROM folder" +
|
||||
" WHERE account = :account AND type = '" + EntityFolder.TYPE_ARCHIVE + "' ")
|
||||
EntityFolder getArchiveFolder(long account);
|
||||
|
||||
@Query("SELECT folder.* FROM folder" +
|
||||
" WHERE account = :account AND type = '" + EntityFolder.TYPE_JUNK + "' ")
|
||||
EntityFolder getSpamFolder(long account);
|
||||
|
||||
@Query("SELECT * FROM folder WHERE type = '" + EntityFolder.TYPE_OUTBOX + "'")
|
||||
EntityFolder getOutbox();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertFolder(EntityFolder folder);
|
||||
|
||||
@Update
|
||||
void updateFolder(EntityFolder folder);
|
||||
|
||||
@Query("DELETE FROM folder WHERE account= :account AND name = :name")
|
||||
int deleteFolder(Long account, String name);
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoIdentity {
|
||||
@Query("SELECT * FROM identity")
|
||||
LiveData<List<EntityIdentity>> liveIdentities();
|
||||
|
||||
@Query("SELECT * FROM identity WHERE synchronize = :synchronize")
|
||||
LiveData<List<EntityIdentity>> liveIdentities(boolean synchronize);
|
||||
|
||||
@Query("SELECT * FROM identity WHERE id = :id")
|
||||
EntityIdentity getIdentity(long id);
|
||||
|
||||
@Query("SELECT * FROM identity WHERE id = :id")
|
||||
LiveData<EntityIdentity> liveIdentity(long id);
|
||||
|
||||
@Query("SELECT * FROM identity ORDER BY id LIMIT 1")
|
||||
LiveData<EntityIdentity> liveFirstIdentity();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertIdentity(EntityIdentity identity);
|
||||
|
||||
@Update
|
||||
void updateIdentity(EntityIdentity identity);
|
||||
|
||||
@Query("UPDATE identity SET `primary` = 0")
|
||||
void resetPrimary();
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoMessage {
|
||||
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_seen) AS unseen" +
|
||||
" FROM folder" +
|
||||
" JOIN message ON folder = folder.id" +
|
||||
" WHERE folder.type = '" + EntityFolder.TYPE_INBOX + "'" +
|
||||
" AND NOT ui_hide" +
|
||||
" AND received IN (SELECT MAX(m.received) FROM message m WHERE m.folder = message.folder GROUP BY m.thread)")
|
||||
LiveData<List<TupleMessageEx>> liveUnifiedInbox();
|
||||
|
||||
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_seen) AS unseen" +
|
||||
" FROM folder" +
|
||||
" JOIN message ON folder = folder.id" +
|
||||
" WHERE folder.id = :folder" +
|
||||
" AND NOT ui_hide" +
|
||||
" AND received IN (SELECT MAX(m.received) FROM message m WHERE m.folder = message.folder GROUP BY m.thread)")
|
||||
LiveData<List<TupleMessageEx>> liveMessages(long folder);
|
||||
|
||||
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_seen) AS unseen" +
|
||||
" FROM message" +
|
||||
" JOIN folder ON folder.id = message.folder" +
|
||||
" JOIN message m1 ON m1.id = :msgid AND m1.account = message.account AND m1.thread = message.thread" +
|
||||
" WHERE NOT message.ui_hide")
|
||||
LiveData<List<TupleMessageEx>> liveThread(long msgid);
|
||||
|
||||
@Query("SELECT * FROM message WHERE id = :id")
|
||||
EntityMessage getMessage(long id);
|
||||
|
||||
@Query("SELECT * FROM message WHERE folder = :folder AND uid = :uid")
|
||||
EntityMessage getMessage(long folder, long uid);
|
||||
|
||||
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_seen) AS unseen" +
|
||||
" FROM message" +
|
||||
" JOIN folder ON folder.id = message.folder" +
|
||||
" WHERE message.id = :id")
|
||||
LiveData<TupleMessageEx> liveMessage(long id);
|
||||
|
||||
@Query("SELECT uid FROM message WHERE folder = :folder AND received >= :received AND NOT uid IS NULL")
|
||||
List<Long> getUids(long folder, long received);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertMessage(EntityMessage message);
|
||||
|
||||
@Update
|
||||
void updateMessage(EntityMessage message);
|
||||
|
||||
@Query("DELETE FROM message WHERE id = :id")
|
||||
void deleteMessage(long id);
|
||||
|
||||
@Query("DELETE FROM message WHERE folder = :folder AND uid = :uid")
|
||||
void deleteMessage(long folder, long uid);
|
||||
|
||||
@Query("DELETE FROM message WHERE folder = :folder")
|
||||
int deleteMessages(long folder);
|
||||
|
||||
@Query("DELETE FROM message WHERE folder = :folder AND received < :received AND NOT uid IS NULL")
|
||||
int deleteMessagesBefore(long folder, long received);
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoOperation {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertOperation(EntityOperation operation);
|
||||
|
||||
@Query("SELECT operation.*, message.uid FROM operation" +
|
||||
" JOIN message ON message.id = operation.message" +
|
||||
" WHERE folder = :folder" +
|
||||
" ORDER BY operation.id")
|
||||
List<TupleOperationEx> getOperations(long folder);
|
||||
|
||||
@Query("DELETE FROM operation WHERE id = :id")
|
||||
void deleteOperation(long id);
|
||||
|
||||
@Query("DELETE FROM operation WHERE message = :id AND name = :name")
|
||||
int deleteOperations(long id, String name);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityAccount.TABLE_NAME,
|
||||
indices = {
|
||||
}
|
||||
)
|
||||
public class EntityAccount {
|
||||
static final String TABLE_NAME = "account";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
public String name;
|
||||
@NonNull
|
||||
public String host; // IMAP
|
||||
@NonNull
|
||||
public Integer port;
|
||||
@NonNull
|
||||
public String user;
|
||||
@NonNull
|
||||
public String password;
|
||||
@NonNull
|
||||
public Boolean primary;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityAccount) {
|
||||
EntityAccount other = (EntityAccount) obj;
|
||||
return (this.name == null ? other.name == null : this.name.equals(other.name) &&
|
||||
this.host.equals(other.host) &&
|
||||
this.port.equals(other.port) &&
|
||||
this.user.equals(other.user) &&
|
||||
this.password.equals(other.password) &&
|
||||
this.primary.equals(other.primary) &&
|
||||
this.synchronize.equals(other.synchronize));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityAttachment.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
@ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"message"})
|
||||
}
|
||||
)
|
||||
public class EntityAttachment {
|
||||
static final String TABLE_NAME = "attachment";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
@NonNull
|
||||
public Long message;
|
||||
@NonNull
|
||||
public String type;
|
||||
public String name;
|
||||
public byte[] content;
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityFolder.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
@ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"account", "name"}, unique = true),
|
||||
@Index(value = {"account"}),
|
||||
@Index(value = {"name"}),
|
||||
@Index(value = {"type"})
|
||||
}
|
||||
)
|
||||
public class EntityFolder {
|
||||
static final String TABLE_NAME = "folder";
|
||||
|
||||
static final String TYPE_INBOX = "Inbox";
|
||||
static final String TYPE_OUTBOX = "Outbox";
|
||||
static final String TYPE_ARCHIVE = "All";
|
||||
static final String TYPE_DRAFTS = "Drafts";
|
||||
static final String TYPE_TRASH = "Trash";
|
||||
static final String TYPE_JUNK = "Junk";
|
||||
static final String TYPE_SENT = "Sent";
|
||||
static final String TYPE_USER = "User";
|
||||
|
||||
static final List<String> STANDARD_FOLDER_ATTR = Arrays.asList(
|
||||
"All",
|
||||
"Drafts",
|
||||
"Trash",
|
||||
"Junk",
|
||||
"Sent"
|
||||
);
|
||||
static final List<String> STANDARD_FOLDER_TYPE = Arrays.asList(
|
||||
TYPE_ARCHIVE,
|
||||
TYPE_DRAFTS,
|
||||
TYPE_TRASH,
|
||||
TYPE_JUNK,
|
||||
TYPE_SENT
|
||||
); // Must match STANDARD_FOLDER_ATTR
|
||||
|
||||
static boolean isOutgoing(String type) {
|
||||
return (TYPE_OUTBOX.equals(type) || TYPE_DRAFTS.equals(type) || TYPE_SENT.equals(type));
|
||||
}
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
public Long account; // Outbox = null
|
||||
@NonNull
|
||||
public String name;
|
||||
@NonNull
|
||||
public String type;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
@NonNull
|
||||
public Integer after; // days
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityFolder) {
|
||||
EntityFolder other = (EntityFolder) obj;
|
||||
return (this.account == null ? other.account == null : this.account.equals(other.account) &&
|
||||
this.name.equals(other.name) &&
|
||||
this.type.equals(other.type) &&
|
||||
this.synchronize.equals(other.synchronize) &&
|
||||
this.after.equals(other.after));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityIdentity.TABLE_NAME,
|
||||
indices = {
|
||||
}
|
||||
)
|
||||
public class EntityIdentity {
|
||||
static final String TABLE_NAME = "identity";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
@NonNull
|
||||
public String name;
|
||||
@NonNull
|
||||
public String email;
|
||||
public String replyto;
|
||||
@NonNull
|
||||
public String host; // SMTP
|
||||
@NonNull
|
||||
public Integer port;
|
||||
@NonNull
|
||||
public Boolean starttls;
|
||||
@NonNull
|
||||
public String user;
|
||||
@NonNull
|
||||
public String password;
|
||||
@NonNull
|
||||
public Boolean primary;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityIdentity) {
|
||||
EntityIdentity other = (EntityIdentity) obj;
|
||||
return (this.name.equals(other.name) &&
|
||||
this.email.equals(other.email) &&
|
||||
this.replyto == null ? other.replyto == null : this.replyto.equals(other.replyto) &&
|
||||
this.host.equals(other.host) &&
|
||||
this.port.equals(other.port) &&
|
||||
this.starttls.equals(other.starttls) &&
|
||||
this.user.equals(other.user) &&
|
||||
this.password.equals(other.password) &&
|
||||
this.primary.equals(other.primary) &&
|
||||
this.synchronize.equals(other.synchronize));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
|
||||
// https://developer.android.com/training/data-storage/room/defining-data
|
||||
|
||||
@Entity(
|
||||
tableName = EntityMessage.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
@ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE),
|
||||
@ForeignKey(childColumns = "folder", entity = EntityFolder.class, parentColumns = "id", onDelete = CASCADE),
|
||||
@ForeignKey(childColumns = "identity", entity = EntityIdentity.class, parentColumns = "id", onDelete = CASCADE),
|
||||
@ForeignKey(childColumns = "replying", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"account"}),
|
||||
@Index(value = {"folder"}),
|
||||
@Index(value = {"identity"}),
|
||||
@Index(value = {"replying"}),
|
||||
@Index(value = {"folder", "uid"}, unique = true),
|
||||
@Index(value = {"thread"}),
|
||||
@Index(value = {"received"})
|
||||
// ui_seen? ui_seen?
|
||||
}
|
||||
)
|
||||
public class EntityMessage {
|
||||
static final String TABLE_NAME = "message";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
public Long account; // performance, compose = null
|
||||
@NonNull
|
||||
public Long folder;
|
||||
public Long identity;
|
||||
public Long replying;
|
||||
public Long uid; // compose = null
|
||||
public String msgid;
|
||||
public String references;
|
||||
public String inreplyto;
|
||||
public String thread; // compose = null
|
||||
public String from;
|
||||
public String to;
|
||||
public String cc;
|
||||
public String bcc;
|
||||
public String reply;
|
||||
public String subject;
|
||||
public String body;
|
||||
public Long sent; // compose = null
|
||||
@NonNull
|
||||
public Long received; // compose = stored
|
||||
@NonNull
|
||||
public Boolean seen;
|
||||
@NonNull
|
||||
public Boolean ui_seen;
|
||||
@NonNull
|
||||
public Boolean ui_hide;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityMessage) {
|
||||
EntityMessage other = (EntityMessage) obj;
|
||||
return (this.account == null ? other.account == null : this.account.equals(other.account) &&
|
||||
this.folder.equals(other.folder) &&
|
||||
this.identity == null ? other.identity == null : this.identity.equals(other.identity) &&
|
||||
this.replying == null ? other.replying == null : this.replying.equals(other.replying) &&
|
||||
this.uid == null ? other.uid == null : this.uid.equals(other.uid) &&
|
||||
this.msgid == null ? other.msgid == null : this.msgid.equals(other.msgid) &&
|
||||
this.references == null ? other.references == null : this.references.equals(other.references) &&
|
||||
this.inreplyto == null ? other.inreplyto == null : this.inreplyto.equals(other.inreplyto) &&
|
||||
this.thread == null ? other.thread == null : thread.equals(other.thread) &&
|
||||
this.from == null ? other.from == null : this.from.equals(other.from) &&
|
||||
this.to == null ? other.to == null : this.to.equals(other.to) &&
|
||||
this.cc == null ? other.cc == null : this.cc.equals(other.cc) &&
|
||||
this.bcc == null ? other.bcc == null : this.bcc.equals(other.bcc) &&
|
||||
this.reply == null ? other.reply == null : this.reply.equals(other.reply) &&
|
||||
this.subject == null ? other.subject == null : this.subject.equals(other.subject) &&
|
||||
this.body == null ? other.body == null : this.body.equals(other.body) &&
|
||||
this.sent == null ? other.sent == null : this.sent.equals(other.sent) &&
|
||||
this.received.equals(other.received) &&
|
||||
this.seen.equals(other.seen) &&
|
||||
this.ui_seen.equals(other.ui_seen) &&
|
||||
this.ui_hide.equals(other.ui_hide));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityOperation.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
@ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"message"})
|
||||
}
|
||||
)
|
||||
public class EntityOperation {
|
||||
static final String TABLE_NAME = "operation";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
@NonNull
|
||||
public Long message;
|
||||
@NonNull
|
||||
public String name;
|
||||
public String args;
|
||||
|
||||
public static final String SEEN = "seen";
|
||||
public static final String ADD = "add";
|
||||
public static final String MOVE = "move";
|
||||
public static final String DELETE = "delete";
|
||||
public static final String SEND = "send";
|
||||
|
||||
static void queue(Context context, EntityMessage message, String name) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
queue(context, message, name, jsonArray);
|
||||
}
|
||||
|
||||
static void queue(Context context, EntityMessage message, String name, Object value) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
jsonArray.put(value);
|
||||
queue(context, message, name, jsonArray);
|
||||
}
|
||||
|
||||
private static void queue(Context context, EntityMessage message, String name, JSONArray jsonArray) {
|
||||
DaoOperation dao = DB.getInstance(context).operation();
|
||||
|
||||
int purged = 0;
|
||||
if (SEEN.equals(name))
|
||||
purged = dao.deleteOperations(message.id, name);
|
||||
|
||||
EntityOperation operation = new EntityOperation();
|
||||
operation.message = message.id;
|
||||
operation.name = name;
|
||||
operation.args = jsonArray.toString();
|
||||
operation.id = dao.insertOperation(operation);
|
||||
|
||||
Log.i(Helper.TAG, "Queued op=" + operation.id + "/" + name +
|
||||
" args=" + operation.args +
|
||||
" msg=" + message.folder + "/" + message.id + " uid=" + message.uid +
|
||||
" purged=" + purged);
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ServiceSynchronize.ACTION_PROCESS_OPERATIONS + message.folder));
|
||||
}
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
|
||||
public class FragmentAccount extends Fragment {
|
||||
private List<Provider> providers;
|
||||
|
||||
private EditText etName;
|
||||
private Spinner spProfile;
|
||||
private EditText etHost;
|
||||
private EditText etPort;
|
||||
private EditText etUser;
|
||||
private EditText etPassword;
|
||||
private CheckBox cbPrimary;
|
||||
private CheckBox cbSynchronize;
|
||||
private Button btnOk;
|
||||
private ProgressBar pbCheck;
|
||||
|
||||
static final int DEFAULT_INBOX_SYNC = 30;
|
||||
static final int DEFAULT_STANDARD_SYNC = 7;
|
||||
|
||||
private static final List<String> standard_sync = Arrays.asList(
|
||||
EntityFolder.TYPE_DRAFTS,
|
||||
EntityFolder.TYPE_SENT
|
||||
);
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_account, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long id = args.getLong("id", -1);
|
||||
|
||||
// Get providers
|
||||
providers = Provider.loadProfiles(getContext());
|
||||
providers.add(0, new Provider(getString(R.string.title_custom)));
|
||||
|
||||
// Get controls
|
||||
spProfile = view.findViewById(R.id.spProvider);
|
||||
etName = view.findViewById(R.id.etName);
|
||||
etHost = view.findViewById(R.id.etHost);
|
||||
etPort = view.findViewById(R.id.etPort);
|
||||
etUser = view.findViewById(R.id.etUser);
|
||||
etPassword = view.findViewById(R.id.etPassword);
|
||||
cbPrimary = view.findViewById(R.id.cbPrimary);
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
btnOk = view.findViewById(R.id.btnOk);
|
||||
pbCheck = view.findViewById(R.id.pbCheck);
|
||||
|
||||
// Wire controls
|
||||
|
||||
spProfile.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
Provider provider = providers.get(position);
|
||||
if (provider.imap_port != 0) {
|
||||
etName.setText(provider.name);
|
||||
etHost.setText(provider.imap_host);
|
||||
etPort.setText(Integer.toString(provider.imap_port));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
ArrayAdapter<Provider> adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers);
|
||||
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
spProfile.setAdapter(adapter);
|
||||
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
btnOk.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
btnOk.setEnabled(false);
|
||||
pbCheck.setVisibility(View.VISIBLE);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putString("name", etName.getText().toString());
|
||||
args.putString("host", etHost.getText().toString());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", etPassword.getText().toString());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
|
||||
getLoaderManager().restartLoader(ActivityView.LOADER_ACCOUNT_PUT, args, putLoaderCallbacks).forceLoad();
|
||||
}
|
||||
});
|
||||
|
||||
DB.getInstance(getContext()).account().liveAccount(id).observe(this, new Observer<EntityAccount>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityAccount account) {
|
||||
etName.setText(account == null ? null : account.name);
|
||||
etHost.setText(account == null ? null : account.host);
|
||||
etPort.setText(account == null ? null : Long.toString(account.port));
|
||||
etUser.setText(account == null ? null : account.user);
|
||||
etPassword.setText(account == null ? null : account.password);
|
||||
cbPrimary.setChecked(account == null ? true : account.primary);
|
||||
cbSynchronize.setChecked(account == null ? true : account.synchronize);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_edit_account);
|
||||
}
|
||||
|
||||
private static class PutLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
PutLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
try {
|
||||
String name = args.getString("name");
|
||||
String host = args.getString("host");
|
||||
String port = args.getString("port");
|
||||
String user = args.getString("user");
|
||||
|
||||
if (TextUtils.isEmpty(name))
|
||||
name = host + "/" + user;
|
||||
if (TextUtils.isEmpty(port))
|
||||
port = "0";
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityAccount account = db.account().getAccount(args.getLong("id"));
|
||||
boolean update = (account != null);
|
||||
if (account == null)
|
||||
account = new EntityAccount();
|
||||
account.name = name;
|
||||
account.host = host;
|
||||
account.port = Integer.parseInt(port);
|
||||
account.user = user;
|
||||
account.password = Objects.requireNonNull(args.getString("password"));
|
||||
account.primary = args.getBoolean("primary");
|
||||
account.synchronize = args.getBoolean("synchronize");
|
||||
|
||||
// Check IMAP server
|
||||
List<EntityFolder> folders = new ArrayList<>();
|
||||
if (account.synchronize) {
|
||||
Session isession = Session.getDefaultInstance(MessageHelper.getSessionProperties(), null);
|
||||
IMAPStore istore = null;
|
||||
try {
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
|
||||
if (!istore.hasCapability("IDLE"))
|
||||
throw new MessagingException(getContext().getString(R.string.title_no_idle));
|
||||
|
||||
boolean drafts = false;
|
||||
for (Folder ifolder : istore.getDefaultFolder().list("*")) {
|
||||
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
|
||||
for (String attr : attrs) {
|
||||
if (attr.startsWith("\\")) {
|
||||
int index = EntityFolder.STANDARD_FOLDER_ATTR.indexOf(attr.substring(1));
|
||||
if (index >= 0) {
|
||||
EntityFolder folder = new EntityFolder();
|
||||
folder.name = ifolder.getFullName();
|
||||
folder.type = EntityFolder.STANDARD_FOLDER_TYPE.get(index);
|
||||
folder.synchronize = standard_sync.contains(folder.type);
|
||||
folder.after = DEFAULT_STANDARD_SYNC;
|
||||
folders.add(folder);
|
||||
|
||||
Log.i(Helper.TAG, "Standard folder=" + folder.name +
|
||||
" type=" + folder.type + " attr=" + TextUtils.join(",", attrs));
|
||||
|
||||
if (EntityFolder.TYPE_DRAFTS.equals(folder.type))
|
||||
drafts = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!drafts)
|
||||
throw new MessagingException(getContext().getString(R.string.title_no_drafts));
|
||||
} finally {
|
||||
if (istore != null)
|
||||
istore.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (account.primary)
|
||||
db.account().resetPrimary();
|
||||
|
||||
if (update)
|
||||
db.account().updateAccount(account);
|
||||
else
|
||||
try {
|
||||
db.beginTransaction();
|
||||
account.id = db.account().insertAccount(account);
|
||||
|
||||
EntityFolder inbox = new EntityFolder();
|
||||
inbox.name = "INBOX";
|
||||
inbox.type = EntityFolder.TYPE_INBOX;
|
||||
inbox.synchronize = true;
|
||||
inbox.after = DEFAULT_INBOX_SYNC;
|
||||
folders.add(0, inbox);
|
||||
|
||||
for (EntityFolder folder : folders) {
|
||||
folder.account = account.id;
|
||||
Log.i(Helper.TAG, "Creating folder=" + folder.name + " (" + folder.type + ")");
|
||||
folder.id = db.folder().insertFolder(folder);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
ServiceSynchronize.restart(getContext(), "account");
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
|
||||
PutLoader loader = new PutLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
btnOk.setEnabled(true);
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
if (ex == null)
|
||||
getFragmentManager().popBackStack();
|
||||
else {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentAccounts extends Fragment {
|
||||
private RecyclerView rvAccount;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterAccount adapter;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_accounts, container, false);
|
||||
|
||||
// Get controls
|
||||
rvAccount = view.findViewById(R.id.rvAccount);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvAccount.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvAccount.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterAccount(getContext());
|
||||
rvAccount.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
FragmentAccount fragment = new FragmentAccount();
|
||||
fragment.setArguments(new Bundle());
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
// Observe accounts
|
||||
DB.getInstance(getContext()).account().liveAccounts().observe(this, new Observer<List<EntityAccount>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAccount> accounts) {
|
||||
adapter.set(accounts);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_list_accounts);
|
||||
}
|
||||
}
|
@ -0,0 +1,585 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.BottomNavigationView;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class FragmentCompose extends Fragment {
|
||||
private boolean once = false;
|
||||
private String thread = null;
|
||||
private long rid = -1;
|
||||
|
||||
private Spinner spFrom;
|
||||
private ImageView ivIdentyAdd;
|
||||
private EditText etTo;
|
||||
private ImageView ivContactAdd;
|
||||
private EditText etSubject;
|
||||
private EditText etBody;
|
||||
private BottomNavigationView bottom_navigation;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_compose, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
String action = args.getString("action");
|
||||
final long id = (TextUtils.isEmpty(action) ? args.getLong("id") : -1);
|
||||
|
||||
// Get controls
|
||||
spFrom = view.findViewById(R.id.spFrom);
|
||||
ivIdentyAdd = view.findViewById(R.id.ivIdentyAdd);
|
||||
etTo = view.findViewById(R.id.etTo);
|
||||
ivContactAdd = view.findViewById(R.id.ivContactAdd);
|
||||
etSubject = view.findViewById(R.id.etSubject);
|
||||
etBody = view.findViewById(R.id.etBody);
|
||||
bottom_navigation = view.findViewById(R.id.bottom_navigation);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
|
||||
etBody.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// Wire controls
|
||||
|
||||
ivIdentyAdd.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", -1);
|
||||
FragmentIdentity fragment = new FragmentIdentity();
|
||||
fragment.setArguments(args);
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
});
|
||||
|
||||
ivContactAdd.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI);
|
||||
startActivityForResult(intent, ActivityCompose.REQUEST_CONTACT);
|
||||
}
|
||||
});
|
||||
|
||||
bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
bottom_navigation.setEnabled(false);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_delete:
|
||||
actionDelete(id);
|
||||
return true;
|
||||
case R.id.action_save:
|
||||
actionPut(id, false);
|
||||
return true;
|
||||
case R.id.action_send:
|
||||
actionPut(id, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
bottom_navigation.getMenu().findItem(R.id.action_delete).setEnabled(id > 0);
|
||||
bottom_navigation.setEnabled(false);
|
||||
|
||||
DB.getInstance(getContext()).identity().liveIdentities(true).observe(getActivity(), new Observer<List<EntityIdentity>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable final List<EntityIdentity> identities) {
|
||||
Collections.sort(identities, new Comparator<EntityIdentity>() {
|
||||
@Override
|
||||
public int compare(EntityIdentity i1, EntityIdentity i2) {
|
||||
return i1.name.compareTo(i2.name);
|
||||
}
|
||||
});
|
||||
|
||||
ArrayAdapter<EntityIdentity> adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, identities);
|
||||
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
spFrom.setAdapter(adapter);
|
||||
|
||||
// Select primary identity, also for saved drafts
|
||||
for (int pos = 0; pos < identities.size(); pos++)
|
||||
if (identities.get(pos).primary) {
|
||||
spFrom.setSelection(pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getLoaderManager().restartLoader(ActivityCompose.LOADER_COMPOSE_GET, getArguments(), getLoaderCallbacks).forceLoad();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_compose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == ActivityCompose.REQUEST_CONTACT && resultCode == RESULT_OK) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = getContext().getContentResolver().query(data.getData(),
|
||||
new String[]{
|
||||
ContactsContract.CommonDataKinds.Email.ADDRESS,
|
||||
ContactsContract.Contacts.DISPLAY_NAME
|
||||
},
|
||||
null, null, null);
|
||||
if (cursor.moveToFirst()) {
|
||||
int colEmail = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS);
|
||||
int colName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
|
||||
String email = cursor.getString(colEmail);
|
||||
String name = cursor.getString(colName);
|
||||
|
||||
InternetAddress address = new InternetAddress(email, name);
|
||||
StringBuilder sb = new StringBuilder(etTo.getText().toString());
|
||||
if (sb.length() > 0)
|
||||
sb.append("; ");
|
||||
sb.append(address.toString());
|
||||
etTo.setText(sb.toString());
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), ex.getMessage(), Toast.LENGTH_LONG).show();
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void actionDelete(final long id) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
getLoaderManager().restartLoader(ActivityCompose.LOADER_COMPOSE_DELETE, args, deleteLoaderCallbacks).forceLoad();
|
||||
}
|
||||
|
||||
private void actionPut(long id, boolean send) {
|
||||
bottom_navigation.setEnabled(false);
|
||||
|
||||
EntityIdentity identity = (EntityIdentity) spFrom.getSelectedItem();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putLong("iid", identity == null ? -1 : identity.id);
|
||||
args.putString("thread", FragmentCompose.this.thread);
|
||||
args.putLong("rid", FragmentCompose.this.rid);
|
||||
args.putString("to", etTo.getText().toString());
|
||||
args.putString("subject", etSubject.getText().toString());
|
||||
args.putString("body", etBody.getText().toString());
|
||||
args.putBoolean("send", send);
|
||||
|
||||
getLoaderManager().restartLoader(ActivityCompose.LOADER_COMPOSE_PUT, args, putLoaderCallbacks).forceLoad();
|
||||
}
|
||||
|
||||
private static class GetLoader extends AsyncTaskLoader<Bundle> {
|
||||
private Bundle args;
|
||||
|
||||
GetLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Bundle loadInBackground() {
|
||||
String action = args.getString("action");
|
||||
long id = args.getLong("id", -1);
|
||||
EntityMessage msg = DB.getInstance(getContext()).message().getMessage(id);
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putString("action", action);
|
||||
|
||||
if (msg != null) {
|
||||
if (msg.identity != null)
|
||||
result.putLong("iid", msg.identity);
|
||||
if (msg.replying != null)
|
||||
result.putLong("rid", msg.replying);
|
||||
result.putString("thread", msg.thread);
|
||||
result.putString("subject", msg.subject);
|
||||
result.putString("body", msg.body);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(action)) {
|
||||
if (msg != null) {
|
||||
result.putString("from", msg.from);
|
||||
result.putString("to", msg.to);
|
||||
}
|
||||
} else if ("reply".equals(action)) {
|
||||
String to = null;
|
||||
if (msg != null)
|
||||
try {
|
||||
Address[] reply = MessageHelper.decodeAddresses(msg.reply);
|
||||
to = (reply.length == 0 ? msg.from : msg.reply);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
result.putLong("rid", msg.id);
|
||||
result.putString("from", msg.to);
|
||||
result.putString("to", to);
|
||||
} else if ("reply_all".equals(action)) {
|
||||
String to = null;
|
||||
if (msg != null) {
|
||||
try {
|
||||
Address[] from = MessageHelper.decodeAddresses(msg.from);
|
||||
Address[] reply = MessageHelper.decodeAddresses(msg.reply);
|
||||
Address[] cc = MessageHelper.decodeAddresses(msg.cc);
|
||||
List<Address> addresses = new ArrayList<>();
|
||||
addresses.addAll(Arrays.asList(reply.length == 0 ? from : reply));
|
||||
addresses.addAll(Arrays.asList(cc));
|
||||
to = MessageHelper.encodeAddresses(addresses.toArray(new Address[0]));
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
result.putLong("rid", msg.id);
|
||||
result.putString("from", msg.to);
|
||||
result.putString("to", to);
|
||||
} else if ("forward".equals(action)) {
|
||||
result.putString("from", msg.to);
|
||||
result.putString("to", null);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks getLoaderCallbacks = new LoaderManager.LoaderCallbacks<Bundle>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Bundle> onCreateLoader(int id, @Nullable Bundle args) {
|
||||
GetLoader loader = new GetLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Bundle> loader, final Bundle result) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
long iid = result.getLong("iid", -1);
|
||||
long rid = result.getLong("rid", -1);
|
||||
String thread = result.getString("thread");
|
||||
String from = result.getString("from");
|
||||
String to = result.getString("to");
|
||||
String subject = result.getString("subject");
|
||||
String body = result.getString("body");
|
||||
String action = result.getString("action");
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
FragmentCompose.this.thread = thread;
|
||||
FragmentCompose.this.rid = rid;
|
||||
|
||||
ArrayAdapter adapter = (ArrayAdapter) spFrom.getAdapter();
|
||||
if (adapter != null)
|
||||
for (int pos = 0; pos < adapter.getCount(); pos++) {
|
||||
EntityIdentity identity = (EntityIdentity) adapter.getItem(pos);
|
||||
if (iid < 0 ? identity.primary : iid == identity.id) {
|
||||
spFrom.setSelection(pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!once) {
|
||||
// Prevent changed fields from being overwritten
|
||||
once = true;
|
||||
|
||||
if (action == null) {
|
||||
if (to != null)
|
||||
etTo.setText(TextUtils.join(", ", MessageHelper.decodeAddresses(to)));
|
||||
etSubject.setText(subject);
|
||||
if (body != null)
|
||||
etBody.setText(Html.fromHtml(HtmlHelper.sanitize(getContext(), body, false)));
|
||||
} else if ("reply".equals(action) || "reply_all".equals(action)) {
|
||||
etTo.setText(TextUtils.join(", ", MessageHelper.decodeAddresses(to)));
|
||||
String text = String.format("<br><br>%s %s:<br><br>%s",
|
||||
Html.escapeHtml(new Date().toString()),
|
||||
Html.escapeHtml(TextUtils.join(", ", MessageHelper.decodeAddresses(from))),
|
||||
HtmlHelper.sanitize(getContext(), body, true));
|
||||
etSubject.setText(getContext().getString(R.string.title_subject_reply, subject));
|
||||
etBody.setText(Html.fromHtml(text));
|
||||
} else if ("forward".equals(action)) {
|
||||
String text = String.format("<br><br>%s %s:<br><br>%s",
|
||||
Html.escapeHtml(new Date().toString()),
|
||||
Html.escapeHtml(TextUtils.join(", ", MessageHelper.decodeAddresses(from))),
|
||||
HtmlHelper.sanitize(getContext(), body, true));
|
||||
etSubject.setText(getContext().getString(R.string.title_subject_forward, subject));
|
||||
etBody.setText(Html.fromHtml(text));
|
||||
}
|
||||
}
|
||||
|
||||
bottom_navigation.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Bundle> loader) {
|
||||
}
|
||||
};
|
||||
|
||||
private static class DeleteLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
DeleteLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
long id = args.getLong("id");
|
||||
DaoMessage message = DB.getInstance(getContext()).message();
|
||||
EntityMessage draft = message.getMessage(id);
|
||||
if (draft != null) {
|
||||
draft.ui_hide = true;
|
||||
message.updateMessage(draft);
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.DELETE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks deleteLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, @Nullable Bundle args) {
|
||||
DeleteLoader loader = new DeleteLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
if (ex == null) {
|
||||
getFragmentManager().popBackStack();
|
||||
Toast.makeText(getContext(), R.string.title_deleted, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
|
||||
private static class PutLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
PutLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
long id = args.getLong("id");
|
||||
boolean send = args.getBoolean("send", false);
|
||||
Log.i(Helper.TAG, "Put load id=" + id + " send=" + send);
|
||||
try {
|
||||
DB db = DB.getInstance(getContext());
|
||||
DaoMessage message = db.message();
|
||||
DaoIdentity identity = db.identity();
|
||||
DaoFolder folder = db.folder();
|
||||
|
||||
// Get data
|
||||
EntityMessage draft = message.getMessage(id);
|
||||
EntityIdentity ident = identity.getIdentity(args.getLong("iid"));
|
||||
EntityFolder drafts = db.folder().getPrimaryDraftFolder();
|
||||
if (drafts == null)
|
||||
throw new Throwable(getContext().getString(R.string.title_no_primary_drafts));
|
||||
|
||||
long rid = args.getLong("rid", -1);
|
||||
String thread = args.getString("thread");
|
||||
String to = args.getString("to");
|
||||
String body = args.getString("body");
|
||||
String subject = args.getString("subject");
|
||||
|
||||
Address afrom = (ident == null ? null : new InternetAddress(ident.email, ident.name));
|
||||
Address ato[] = (TextUtils.isEmpty(to) ? new Address[0] : InternetAddress.parse(to));
|
||||
|
||||
// Build draft
|
||||
boolean update = (draft != null);
|
||||
if (draft == null)
|
||||
draft = new EntityMessage();
|
||||
draft.account = drafts.account;
|
||||
draft.folder = drafts.id;
|
||||
draft.identity = (ident == null ? null : ident.id);
|
||||
draft.replying = (rid < 0 ? null : rid);
|
||||
draft.thread = thread;
|
||||
draft.from = (afrom == null ? null : MessageHelper.encodeAddresses(new Address[]{afrom}));
|
||||
draft.to = (ato == null ? null : MessageHelper.encodeAddresses(ato));
|
||||
draft.subject = subject;
|
||||
draft.body = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
draft.received = new Date().getTime();
|
||||
draft.seen = false;
|
||||
draft.ui_seen = false;
|
||||
draft.ui_hide = send;
|
||||
|
||||
// Store draft
|
||||
if (update)
|
||||
message.updateMessage(draft);
|
||||
else
|
||||
draft.id = message.insertMessage(draft);
|
||||
|
||||
// Check data
|
||||
if (send) {
|
||||
if (draft.identity == null)
|
||||
throw new MessagingException(getContext().getString(R.string.title_from_missing));
|
||||
if (draft.to == null)
|
||||
throw new MessagingException(getContext().getString(R.string.title_to_missing));
|
||||
|
||||
// Get outbox
|
||||
EntityFolder outbox = folder.getOutbox();
|
||||
if (outbox == null) {
|
||||
outbox = new EntityFolder();
|
||||
outbox.name = "OUTBOX";
|
||||
outbox.type = EntityFolder.TYPE_OUTBOX;
|
||||
outbox.synchronize = false;
|
||||
outbox.after = 0;
|
||||
outbox.id = folder.insertFolder(outbox);
|
||||
}
|
||||
|
||||
// Build outgoing message
|
||||
EntityMessage out = new EntityMessage();
|
||||
out.folder = outbox.id;
|
||||
out.identity = draft.identity;
|
||||
out.replying = draft.replying;
|
||||
out.thread = draft.thread;
|
||||
out.from = draft.from;
|
||||
out.to = draft.to;
|
||||
out.subject = draft.subject;
|
||||
out.body = draft.body;
|
||||
out.received = draft.received;
|
||||
out.seen = draft.seen;
|
||||
out.ui_seen = draft.ui_seen;
|
||||
out.ui_hide = false;
|
||||
out.id = message.insertMessage(out);
|
||||
|
||||
EntityOperation.queue(getContext(), out, EntityOperation.SEND);
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.DELETE);
|
||||
} else
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.ADD);
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
private Bundle args;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
|
||||
this.args = args;
|
||||
PutLoader loader = new PutLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
boolean send = args.getBoolean("send", false);
|
||||
Log.i(Helper.TAG, "Put finished send=" + send + " ex=" + ex);
|
||||
|
||||
if (ex == null) {
|
||||
getFragmentManager().popBackStack();
|
||||
Toast.makeText(getContext(), send ? R.string.title_queued : R.string.title_saved, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
public class FragmentEula extends Fragment {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_eula, container, false);
|
||||
|
||||
Button btnAgree = view.findViewById(R.id.btnOk);
|
||||
Button btnDisagree = view.findViewById(R.id.btnCancel);
|
||||
|
||||
btnAgree.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
prefs.edit().putBoolean("eula", true).apply();
|
||||
}
|
||||
});
|
||||
|
||||
btnDisagree.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class FragmentFolder extends Fragment {
|
||||
private CheckBox cbSynchronize;
|
||||
private EditText etAfter;
|
||||
private Button btnOk;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_folder, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long id = args.getLong("id");
|
||||
|
||||
// Get controls
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
etAfter = view.findViewById(R.id.etAfter);
|
||||
btnOk = view.findViewById(R.id.btnOk);
|
||||
|
||||
btnOk.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
btnOk.setEnabled(false);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
args.putString("after", etAfter.getText().toString());
|
||||
|
||||
getLoaderManager().restartLoader(ActivityView.LOADER_FOLDER_PUT, args, putLoaderCallbacks).forceLoad();
|
||||
}
|
||||
});
|
||||
|
||||
DB.getInstance(getContext()).folder().liveFolder(id).observe(this, new Observer<EntityFolder>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityFolder folder) {
|
||||
if (folder != null) {
|
||||
cbSynchronize.setChecked(folder.synchronize);
|
||||
etAfter.setText(Integer.toString(folder.after));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_edit_folder);
|
||||
}
|
||||
|
||||
private static class PutLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
PutLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
try {
|
||||
long id = args.getLong("id");
|
||||
boolean synchronize = args.getBoolean("synchronize");
|
||||
String after = args.getString("after");
|
||||
int days = (TextUtils.isEmpty(after) ? 7 : Integer.parseInt(after));
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
DaoFolder dao = db.folder();
|
||||
EntityFolder folder = dao.getFolder(id);
|
||||
folder.synchronize = synchronize;
|
||||
folder.after = days;
|
||||
dao.updateFolder(folder);
|
||||
|
||||
if (!folder.synchronize)
|
||||
db.message().deleteMessages(folder.id);
|
||||
|
||||
ServiceSynchronize.restart(getContext(), "folder");
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
|
||||
PutLoader loader = new PutLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
btnOk.setEnabled(true);
|
||||
|
||||
if (ex == null)
|
||||
getFragmentManager().popBackStack();
|
||||
else {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentFolders extends Fragment {
|
||||
private RecyclerView rvFolder;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterFolder adapter;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_folders, container, false);
|
||||
|
||||
// Get controls
|
||||
rvFolder = view.findViewById(R.id.rvFolder);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvFolder.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvFolder.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterFolder(getContext());
|
||||
rvFolder.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
}
|
||||
});
|
||||
fab.setVisibility(View.GONE);
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
// Observe folders
|
||||
DB.getInstance(getContext()).folder().liveFolders().observe(this, new Observer<List<TupleFolderEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<TupleFolderEx> folders) {
|
||||
adapter.set(folders);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_list_folders);
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentIdentities extends Fragment {
|
||||
private RecyclerView rvIdentity;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterIdentity adapter;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_identities, container, false);
|
||||
|
||||
// Get controls
|
||||
rvIdentity = view.findViewById(R.id.rvIdentity);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvIdentity.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvIdentity.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterIdentity(getContext());
|
||||
rvIdentity.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
FragmentIdentity fragment = new FragmentIdentity();
|
||||
fragment.setArguments(new Bundle());
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
// Observe identities
|
||||
DB.getInstance(getContext()).identity().liveIdentities().observe(this, new Observer<List<EntityIdentity>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityIdentity> identities) {
|
||||
adapter.set(identities);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_list_identities);
|
||||
}
|
||||
}
|
@ -0,0 +1,289 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Session;
|
||||
import javax.mail.Transport;
|
||||
|
||||
public class FragmentIdentity extends Fragment {
|
||||
private List<Provider> providers;
|
||||
|
||||
private Spinner spProfile;
|
||||
private EditText etName;
|
||||
private EditText etEmail;
|
||||
private EditText etHost;
|
||||
private CheckBox cbStartTls;
|
||||
private EditText etPort;
|
||||
private EditText etUser;
|
||||
private EditText etPassword;
|
||||
private CheckBox cbPrimary;
|
||||
private CheckBox cbSynchronize;
|
||||
private Button btnOk;
|
||||
private ProgressBar pbCheck;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_identity, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long id = args.getLong("id", -1);
|
||||
|
||||
// Get providers
|
||||
providers = Provider.loadProfiles(getContext());
|
||||
providers.add(0, new Provider(getString(R.string.title_custom)));
|
||||
|
||||
// Get controls
|
||||
spProfile = view.findViewById(R.id.spProvider);
|
||||
etName = view.findViewById(R.id.etName);
|
||||
etEmail = view.findViewById(R.id.etEmail);
|
||||
etHost = view.findViewById(R.id.etHost);
|
||||
cbStartTls = view.findViewById(R.id.cbStartTls);
|
||||
etPort = view.findViewById(R.id.etPort);
|
||||
etUser = view.findViewById(R.id.etUser);
|
||||
etPassword = view.findViewById(R.id.etPassword);
|
||||
cbPrimary = view.findViewById(R.id.cbPrimary);
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
btnOk = view.findViewById(R.id.btnOk);
|
||||
pbCheck = view.findViewById(R.id.pbCheck);
|
||||
|
||||
// Wire controls
|
||||
|
||||
etEmail.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
etUser.setText(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
|
||||
etPort.setHint(checked ? "587" : "465");
|
||||
}
|
||||
});
|
||||
|
||||
spProfile.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
Provider provider = providers.get(position);
|
||||
if (provider.smtp_port != 0) {
|
||||
etHost.setText(provider.smtp_host);
|
||||
etPort.setText(Integer.toString(provider.smtp_port));
|
||||
cbStartTls.setChecked(provider.starttls);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
ArrayAdapter<Provider> adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers);
|
||||
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
spProfile.setAdapter(adapter);
|
||||
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
btnOk.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
btnOk.setEnabled(false);
|
||||
pbCheck.setVisibility(View.VISIBLE);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putString("name", etName.getText().toString());
|
||||
args.putString("email", etEmail.getText().toString());
|
||||
args.putString("host", etHost.getText().toString());
|
||||
args.putBoolean("starttls", cbStartTls.isChecked());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", etPassword.getText().toString());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
|
||||
getLoaderManager().restartLoader(ActivityView.LOADER_IDENTITY_PUT, args, putLoaderCallbacks).forceLoad();
|
||||
}
|
||||
});
|
||||
|
||||
DB.getInstance(getContext()).identity().liveIdentity(id).observe(this, new Observer<EntityIdentity>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityIdentity identity) {
|
||||
etName.setText(identity == null ? null : identity.name);
|
||||
etEmail.setText(identity == null ? null : identity.email);
|
||||
etHost.setText(identity == null ? null : identity.host);
|
||||
cbStartTls.setChecked(identity == null ? false : identity.starttls);
|
||||
etPort.setText(identity == null ? null : Long.toString(identity.port));
|
||||
etUser.setText(identity == null ? null : identity.user);
|
||||
etPassword.setText(identity == null ? null : identity.password);
|
||||
cbPrimary.setChecked(identity == null ? true : identity.primary);
|
||||
cbSynchronize.setChecked(identity == null ? true : identity.synchronize);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_edit_indentity);
|
||||
}
|
||||
|
||||
private static class PutLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
PutLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
try {
|
||||
long id = args.getLong("id");
|
||||
String host = args.getString("host");
|
||||
boolean starttls = args.getBoolean("starttls");
|
||||
String port = args.getString("port");
|
||||
if (TextUtils.isEmpty(port))
|
||||
port = "0";
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityIdentity identity = db.identity().getIdentity(id);
|
||||
boolean update = (identity != null);
|
||||
if (identity == null)
|
||||
identity = new EntityIdentity();
|
||||
identity.name = Objects.requireNonNull(args.getString("name"));
|
||||
identity.email = Objects.requireNonNull(args.getString("email"));
|
||||
identity.host = host;
|
||||
identity.port = Integer.parseInt(port);
|
||||
identity.starttls = starttls;
|
||||
identity.user = Objects.requireNonNull(args.getString("user"));
|
||||
identity.password = Objects.requireNonNull(args.getString("password"));
|
||||
identity.primary = args.getBoolean("primary");
|
||||
identity.synchronize = args.getBoolean("synchronize");
|
||||
|
||||
if (TextUtils.isEmpty(identity.name))
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.title_no_name));
|
||||
|
||||
if (TextUtils.isEmpty(identity.email))
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.title_no_email));
|
||||
|
||||
// Check SMTP server
|
||||
if (identity.synchronize) {
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Session isession = Session.getDefaultInstance(props, null);
|
||||
Transport itransport = isession.getTransport(identity.starttls ? "smtp" : "smtps");
|
||||
try {
|
||||
itransport.connect(identity.host, identity.port, identity.user, identity.password);
|
||||
} finally {
|
||||
itransport.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (identity.primary)
|
||||
db.identity().resetPrimary();
|
||||
|
||||
if (update)
|
||||
db.identity().updateIdentity(identity);
|
||||
else
|
||||
identity.id = db.identity().insertIdentity(identity);
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
|
||||
PutLoader loader = new PutLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
btnOk.setEnabled(true);
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
if (ex == null)
|
||||
getFragmentManager().popBackStack();
|
||||
else {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,329 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.BottomNavigationView;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class FragmentMessage extends Fragment {
|
||||
private TextView tvTime;
|
||||
private TextView tvFrom;
|
||||
private TextView tvSubject;
|
||||
private TextView tvCount;
|
||||
private BottomNavigationView top_navigation;
|
||||
private TextView tvBody;
|
||||
private BottomNavigationView bottom_navigation;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
|
||||
private LiveData<TupleFolderEx> liveFolder;
|
||||
|
||||
private ExecutorService executor = Executors.newCachedThreadPool();
|
||||
private DateFormat df = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_message, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long folder = args.getLong("folder");
|
||||
final long id = args.getLong("id");
|
||||
|
||||
// Get controls
|
||||
tvFrom = view.findViewById(R.id.tvAddress);
|
||||
tvTime = view.findViewById(R.id.tvTime);
|
||||
tvSubject = view.findViewById(R.id.tvSubject);
|
||||
tvCount = view.findViewById(R.id.tvCount);
|
||||
top_navigation = view.findViewById(R.id.top_navigation);
|
||||
tvBody = view.findViewById(R.id.tvBody);
|
||||
bottom_navigation = view.findViewById(R.id.bottom_navigation);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
|
||||
tvTime.setTextIsSelectable(true);
|
||||
tvFrom.setTextIsSelectable(true);
|
||||
tvSubject.setTextIsSelectable(true);
|
||||
tvBody.setTextIsSelectable(true);
|
||||
tvBody.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// Wire controls
|
||||
|
||||
top_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_seen:
|
||||
onActionSeen(id);
|
||||
return true;
|
||||
case R.id.action_thread:
|
||||
onActionThread(id);
|
||||
return true;
|
||||
case R.id.action_move:
|
||||
onActionMove(id);
|
||||
return true;
|
||||
case R.id.action_forward:
|
||||
onActionForward(id);
|
||||
return true;
|
||||
case R.id.action_reply_all:
|
||||
onActionReplyAll(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_delete:
|
||||
onActionDelete(id);
|
||||
return true;
|
||||
case R.id.action_spam:
|
||||
onActionSpam(id);
|
||||
return true;
|
||||
case R.id.action_archive:
|
||||
onActionArchive(id);
|
||||
return true;
|
||||
case R.id.action_reply:
|
||||
onActionReply(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
|
||||
// Observe folder
|
||||
liveFolder = db.folder().liveFolderEx(folder);
|
||||
|
||||
// Observe message
|
||||
db.message().liveMessage(id).observe(this, new Observer<TupleMessageEx>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable TupleMessageEx message) {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
if (message == null || message.ui_hide) {
|
||||
// Message gone (moved, deleted)
|
||||
if (FragmentMessage.this.isVisible())
|
||||
getFragmentManager().popBackStack();
|
||||
} else {
|
||||
tvFrom.setText(message.from == null ? null : MessageHelper.getFormattedAddresses(message.from));
|
||||
tvTime.setText(message.sent == null ? null : df.format(new Date(message.sent)));
|
||||
tvSubject.setText(message.subject);
|
||||
tvCount.setText(Integer.toString(message.count));
|
||||
tvCount.setVisibility(message.count > 1 ? View.VISIBLE : View.GONE);
|
||||
|
||||
int visibility = (message.ui_seen ? Typeface.NORMAL : Typeface.BOLD);
|
||||
tvFrom.setTypeface(null, visibility);
|
||||
tvTime.setTypeface(null, visibility);
|
||||
tvSubject.setTypeface(null, visibility);
|
||||
tvCount.setTypeface(null, visibility);
|
||||
|
||||
MenuItem actionSeen = top_navigation.getMenu().findItem(R.id.action_seen);
|
||||
actionSeen.setIcon(message.ui_seen
|
||||
? R.drawable.baseline_visibility_off_24
|
||||
: R.drawable.baseline_visibility_24);
|
||||
actionSeen.setTitle(message.ui_seen ? R.string.title_unseen : R.string.title_seen);
|
||||
|
||||
tvBody.setText(message.body == null
|
||||
? null
|
||||
: Html.fromHtml(HtmlHelper.sanitize(getContext(), message.body, false)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
liveFolder.observe(this, folderObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
liveFolder.removeObservers(this);
|
||||
}
|
||||
|
||||
Observer<TupleFolderEx> folderObserver = new Observer<TupleFolderEx>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable TupleFolderEx folder) {
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(folder == null
|
||||
? null
|
||||
: Helper.localizeFolderName(getContext(), folder));
|
||||
}
|
||||
};
|
||||
|
||||
private void onActionSeen(final long id) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
message.ui_seen = !message.ui_seen;
|
||||
db.message().updateMessage(message);
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.SEEN, message.ui_seen);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onActionThread(long id) {
|
||||
FragmentMessages fragment = new FragmentMessages();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("thread", id); // message ID
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("thread");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onActionMove(final long id) {
|
||||
Toast.makeText(getContext(), "Not implemented yet", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void onActionForward(long id) {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class)
|
||||
.putExtra("id", id)
|
||||
.putExtra("action", "forward"));
|
||||
}
|
||||
|
||||
private void onActionReplyAll(long id) {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class)
|
||||
.putExtra("id", id)
|
||||
.putExtra("action", "reply_all"));
|
||||
}
|
||||
|
||||
private void onActionDelete(final long id) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder
|
||||
.setMessage(R.string.title_ask_delete)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
message.ui_hide = true;
|
||||
db.message().updateMessage(message);
|
||||
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.DELETE);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null).show();
|
||||
}
|
||||
|
||||
private void onActionSpam(final long id) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder
|
||||
.setMessage(R.string.title_ask_spam)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
EntityFolder spam = db.folder().getSpamFolder(message.account);
|
||||
if (spam == null) {
|
||||
Toast.makeText(getContext(), R.string.title_no_spam, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
message.ui_hide = true;
|
||||
db.message().updateMessage(message);
|
||||
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.MOVE, spam.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null).show();
|
||||
}
|
||||
|
||||
private void onActionArchive(final long id) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
EntityFolder archive = db.folder().getArchiveFolder(message.account);
|
||||
if (archive == null) {
|
||||
Toast.makeText(getContext(), R.string.title_no_archive, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
message.ui_hide = true;
|
||||
db.message().updateMessage(message);
|
||||
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.MOVE, archive.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onActionReply(long id) {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class)
|
||||
.putExtra("id", id)
|
||||
.putExtra("action", "reply"));
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentMessages extends Fragment {
|
||||
private RecyclerView rvMessage;
|
||||
private TextView tvNoEmail;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterMessage adapter;
|
||||
private LiveData<TupleFolderEx> liveFolder;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_messages, container, false);
|
||||
|
||||
// Get arguments
|
||||
long folder = getArguments().getLong("folder", -1);
|
||||
long thread = getArguments().getLong("thread", -1); // message ID
|
||||
|
||||
// Get controls
|
||||
rvMessage = view.findViewById(R.id.rvFolder);
|
||||
tvNoEmail = view.findViewById(R.id.tvNoEmail);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvMessage.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvMessage.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterMessage(getContext(),
|
||||
thread < 0
|
||||
? AdapterMessage.ViewType.FOLDER
|
||||
: AdapterMessage.ViewType.THREAD);
|
||||
rvMessage.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class));
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
|
||||
// Observe folder
|
||||
liveFolder = (thread < 0 ? DB.getInstance(getContext()).folder().liveFolderEx(folder) : null);
|
||||
|
||||
// Observe messages
|
||||
if (thread < 0)
|
||||
if (folder < 0)
|
||||
db.message().liveUnifiedInbox().observe(this, messagesObserver);
|
||||
else
|
||||
db.message().liveMessages(folder).observe(this, messagesObserver);
|
||||
else {
|
||||
db.message().liveThread(thread).observe(this, messagesObserver);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (liveFolder == null)
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_folder_thread);
|
||||
else
|
||||
liveFolder.observe(this, folderObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (liveFolder != null)
|
||||
liveFolder.removeObservers(this);
|
||||
}
|
||||
|
||||
Observer<TupleFolderEx> folderObserver = new Observer<TupleFolderEx>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable TupleFolderEx folder) {
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(folder.name == null
|
||||
? getString(R.string.title_folder_unified)
|
||||
: Helper.localizeFolderName(getContext(), folder));
|
||||
}
|
||||
};
|
||||
|
||||
Observer<List<TupleMessageEx>> messagesObserver = new Observer<List<TupleMessageEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<TupleMessageEx> messages) {
|
||||
adapter.set(messages);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
if (messages.size() == 0) {
|
||||
tvNoEmail.setVisibility(View.VISIBLE);
|
||||
rvMessage.setVisibility(View.GONE);
|
||||
} else {
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
rvMessage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.Manifest;
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentSetup extends Fragment {
|
||||
private Button btnAccount;
|
||||
private Button btnIdentity;
|
||||
private Button btnPermissions;
|
||||
private ProgressBar pbAccount;
|
||||
private ProgressBar pbIdentity;
|
||||
private TextView tvAccountDone;
|
||||
private TextView tvIdentityDone;
|
||||
private TextView tvPermissionsDone;
|
||||
|
||||
private static final String[] permissions = new String[]{
|
||||
Manifest.permission.READ_CONTACTS
|
||||
};
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_setup, container, false);
|
||||
|
||||
// Get controls
|
||||
btnAccount = view.findViewById(R.id.btnAccount);
|
||||
btnIdentity = view.findViewById(R.id.btnIdentity);
|
||||
btnPermissions = view.findViewById(R.id.btnPermissions);
|
||||
pbAccount = view.findViewById(R.id.pbAccount);
|
||||
pbIdentity = view.findViewById(R.id.pbIdentity);
|
||||
tvAccountDone = view.findViewById(R.id.tvAccountDone);
|
||||
tvIdentityDone = view.findViewById(R.id.tvIdentityDone);
|
||||
tvPermissionsDone = view.findViewById(R.id.tvPermissionsDone);
|
||||
|
||||
// Wire controls
|
||||
|
||||
btnAccount.setOnClickListener(new View.OnClickListener() {
|
||||
private boolean once;
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
once = false;
|
||||
btnAccount.setEnabled(false);
|
||||
pbAccount.setVisibility(View.VISIBLE);
|
||||
|
||||
DB.getInstance(getContext()).account().liveFirstAccount().observe(getActivity(), new Observer<EntityAccount>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityAccount account) {
|
||||
btnAccount.setEnabled(true);
|
||||
pbAccount.setVisibility(View.GONE);
|
||||
|
||||
if (!once) {
|
||||
once = true;
|
||||
Bundle args = new Bundle();
|
||||
if (account != null)
|
||||
args.putLong("id", account.id);
|
||||
FragmentAccount fragment = new FragmentAccount();
|
||||
fragment.setArguments(args);
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
btnIdentity.setOnClickListener(new View.OnClickListener() {
|
||||
private boolean once;
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
once = false;
|
||||
btnIdentity.setEnabled(false);
|
||||
pbIdentity.setVisibility(View.VISIBLE);
|
||||
|
||||
DB.getInstance(getContext()).identity().liveFirstIdentity().observe(getActivity(), new Observer<EntityIdentity>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityIdentity identity) {
|
||||
btnIdentity.setEnabled(true);
|
||||
pbIdentity.setVisibility(View.GONE);
|
||||
|
||||
if (!once) {
|
||||
once = true;
|
||||
Bundle args = new Bundle();
|
||||
if (identity != null)
|
||||
args.putLong("id", identity.id);
|
||||
FragmentIdentity fragment = new FragmentIdentity();
|
||||
fragment.setArguments(args);
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
btnPermissions.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
requestPermissions(permissions, 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
|
||||
pbAccount.setVisibility(View.GONE);
|
||||
pbIdentity.setVisibility(View.GONE);
|
||||
tvAccountDone.setVisibility(View.INVISIBLE);
|
||||
tvIdentityDone.setVisibility(View.INVISIBLE);
|
||||
tvPermissionsDone.setVisibility(View.INVISIBLE);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
|
||||
db.account().liveAccounts(true).observe(this, new Observer<List<EntityAccount>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAccount> accounts) {
|
||||
tvAccountDone.setVisibility(accounts.size() > 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
db.identity().liveIdentities(true).observe(this, new Observer<List<EntityIdentity>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityIdentity> identities) {
|
||||
tvIdentityDone.setVisibility(identities.size() > 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
int[] grantResults = new int[permissions.length];
|
||||
for (int i = 0; i < permissions.length; i++)
|
||||
grantResults[i] = ContextCompat.checkSelfPermission(getActivity(), permissions[i]);
|
||||
|
||||
onRequestPermissionsResult(0, permissions, grantResults);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_setup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
boolean has = (grantResults.length > 0);
|
||||
for (int result : grantResults)
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
has = false;
|
||||
break;
|
||||
}
|
||||
|
||||
btnPermissions.setEnabled(!has);
|
||||
tvPermissionsDone.setVisibility(has ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
|
||||
public class Helper {
|
||||
static final String TAG = BuildConfig.APPLICATION_ID;
|
||||
|
||||
static int resolveColor(Context context, int attr) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(attr, typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
static String localizeFolderName(Context context, String name) {
|
||||
if ("INBOX".equals(name))
|
||||
return context.getString(R.string.title_folder_inbox);
|
||||
else if ("OUTBOX".equals(name))
|
||||
return context.getString(R.string.title_folder_outbox);
|
||||
else
|
||||
return name;
|
||||
}
|
||||
|
||||
static String localizeFolderName(Context context, TupleFolderEx folder) {
|
||||
if (TextUtils.isEmpty(folder.accountName))
|
||||
return localizeFolderName(context, folder.name);
|
||||
else
|
||||
return localizeFolderName(context, folder.name) + "/" + folder.accountName;
|
||||
}
|
||||
|
||||
static String formatThrowable(Throwable ex) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(ex.getMessage());
|
||||
Throwable cause = ex.getCause();
|
||||
while (cause != null) {
|
||||
sb.append(" ").append(cause.getMessage());
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.helper.StringUtil;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.nodes.TextNode;
|
||||
import org.jsoup.select.NodeTraversor;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HtmlHelper implements NodeVisitor {
|
||||
private Context context;
|
||||
private String newline;
|
||||
private List<String> links = new ArrayList<>();
|
||||
private StringBuilder sb = new StringBuilder();
|
||||
|
||||
private HtmlHelper(Context context, boolean reply) {
|
||||
this.context = context;
|
||||
this.newline = (reply ? "<br>> " : "<br>");
|
||||
}
|
||||
|
||||
public void head(Node node, int depth) {
|
||||
String name = node.nodeName();
|
||||
if (node instanceof TextNode)
|
||||
sb.append(((TextNode) node).text());
|
||||
else if (name.equals("li"))
|
||||
sb.append(newline).append(" * ");
|
||||
else if (name.equals("dt"))
|
||||
sb.append(" ");
|
||||
else if (StringUtil.in(name, "p", "h1", "h2", "h3", "h4", "h5", "tr", "div"))
|
||||
sb.append(newline);
|
||||
}
|
||||
|
||||
public void tail(Node node, int depth) {
|
||||
String name = node.nodeName();
|
||||
if (StringUtil.in(name, "br", "dd", "dt", "p", "h1", "h2", "h3", "h4", "h5", "div"))
|
||||
sb.append(newline);
|
||||
else if (name.equals("a")) {
|
||||
String link = node.absUrl("href");
|
||||
if (!TextUtils.isEmpty(link)) {
|
||||
if (!links.contains(link))
|
||||
links.add(link);
|
||||
sb.append(" ").append(context.getString(R.string.title_link, link, links.size()));
|
||||
}
|
||||
} else if (name.equals("img")) {
|
||||
String link = node.absUrl("src");
|
||||
if (!TextUtils.isEmpty(link)) {
|
||||
if (!links.contains(link))
|
||||
links.add(link);
|
||||
sb.append(" ").append(context.getString(R.string.title_image, link, links.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (links.size() > 0)
|
||||
sb.append(newline).append(newline);
|
||||
for (int i = 0; i < links.size(); i++)
|
||||
sb.append(String.format("[%d] %s ", i + 1, links.get(i))).append(newline);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String sanitize(Context context, String html, boolean reply) {
|
||||
Document document = Jsoup.parse(html);
|
||||
HtmlHelper visitor = new HtmlHelper(context, reply);
|
||||
NodeTraversor.traverse(visitor, document.body());
|
||||
return visitor.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.Flags;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Multipart;
|
||||
import javax.mail.Part;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
public class MessageHelper {
|
||||
private MimeMessage imessage;
|
||||
private String raw = null;
|
||||
|
||||
static Properties getSessionProperties() {
|
||||
Properties props = new Properties();
|
||||
|
||||
// https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#properties
|
||||
props.put("mail.imaps.ssl.checkserveridentity", "true");
|
||||
props.put("mail.imaps.ssl.trust", "*");
|
||||
props.put("mail.imaps.starttls.enable", "false");
|
||||
props.put("mail.imaps.timeout", "20000");
|
||||
props.put("mail.imaps.connectiontimeout", "20000");
|
||||
|
||||
// https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#properties
|
||||
props.put("mail.smtps.ssl.checkserveridentity", "true");
|
||||
props.put("mail.smtps.ssl.trust", "*");
|
||||
props.put("mail.smtps.starttls.enable", "false");
|
||||
props.put("mail.smtps.starttls.required", "false");
|
||||
props.put("mail.smtps.auth", "true");
|
||||
props.put("mail.smtps.timeout", "20000");
|
||||
props.put("mail.smtps.connectiontimeout", "20000");
|
||||
|
||||
props.put("mail.smtp.ssl.checkserveridentity", "true");
|
||||
props.put("mail.smtp.ssl.trust", "*");
|
||||
props.put("mail.smtp.starttls.enable", "true");
|
||||
props.put("mail.smtp.starttls.required", "true");
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.timeout", "20000");
|
||||
props.put("mail.smtp.connectiontimeout", "20000");
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
static MimeMessage from(EntityMessage message, Session isession) throws MessagingException {
|
||||
MimeMessage imessage = new MimeMessage(isession);
|
||||
|
||||
if (message.from != null)
|
||||
imessage.setFrom(MessageHelper.decodeAddresses(message.from)[0]);
|
||||
|
||||
if (message.to != null)
|
||||
imessage.setRecipients(Message.RecipientType.TO, MessageHelper.decodeAddresses(message.to));
|
||||
|
||||
if (message.cc != null)
|
||||
imessage.setRecipients(Message.RecipientType.CC, MessageHelper.decodeAddresses(message.to));
|
||||
|
||||
if (message.subject != null)
|
||||
imessage.setSubject(message.subject);
|
||||
|
||||
if (message.body != null)
|
||||
imessage.setText(message.body, null, "html");
|
||||
|
||||
imessage.setSentDate(new Date());
|
||||
|
||||
return imessage;
|
||||
}
|
||||
|
||||
static MimeMessage from(EntityMessage message, EntityMessage reply, Session isession) throws MessagingException {
|
||||
MimeMessage imessage = from(message, isession);
|
||||
imessage.addHeader("In-Reply-To", reply.msgid);
|
||||
imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid);
|
||||
return imessage;
|
||||
}
|
||||
|
||||
MessageHelper(MimeMessage message) {
|
||||
this.imessage = message;
|
||||
}
|
||||
|
||||
MessageHelper(String raw, Session isession) throws MessagingException {
|
||||
byte[] bytes = Base64.decode(raw, Base64.URL_SAFE);
|
||||
InputStream is = new ByteArrayInputStream(bytes);
|
||||
this.imessage = new MimeMessage(isession, is);
|
||||
}
|
||||
|
||||
String getMessageID() throws MessagingException {
|
||||
return imessage.getHeader("Message-ID", null);
|
||||
}
|
||||
|
||||
String[] getReferences() throws MessagingException {
|
||||
String refs = imessage.getHeader("References", null);
|
||||
return (refs == null ? new String[0] : refs.split("\\s+"));
|
||||
}
|
||||
|
||||
String getInReplyTo() throws MessagingException {
|
||||
return imessage.getHeader("In-Reply-To", null);
|
||||
}
|
||||
|
||||
String getThreadId(long uid) throws MessagingException {
|
||||
for (String ref : getReferences())
|
||||
if (!TextUtils.isEmpty(ref))
|
||||
return ref;
|
||||
String msgid = getMessageID();
|
||||
return (TextUtils.isEmpty(msgid) ? Long.toString(uid) : msgid);
|
||||
}
|
||||
|
||||
String getFrom() throws MessagingException, JSONException {
|
||||
return encodeAddresses(imessage.getFrom());
|
||||
}
|
||||
|
||||
String getTo() throws MessagingException, JSONException {
|
||||
return encodeAddresses(imessage.getRecipients(Message.RecipientType.TO));
|
||||
}
|
||||
|
||||
String getCc() throws MessagingException, JSONException {
|
||||
return encodeAddresses(imessage.getRecipients(Message.RecipientType.CC));
|
||||
}
|
||||
|
||||
String getReply() throws MessagingException, JSONException {
|
||||
return encodeAddresses(imessage.getReplyTo());
|
||||
}
|
||||
|
||||
static String encodeAddresses(Address[] addresses) throws JSONException {
|
||||
JSONArray jaddresses = new JSONArray();
|
||||
if (addresses != null)
|
||||
for (Address address : addresses)
|
||||
if (address instanceof InternetAddress) {
|
||||
String a = ((InternetAddress) address).getAddress();
|
||||
String p = ((InternetAddress) address).getPersonal();
|
||||
JSONObject jaddress = new JSONObject();
|
||||
if (a != null)
|
||||
jaddress.put("address", a);
|
||||
if (p != null)
|
||||
jaddress.put("personal", p);
|
||||
jaddresses.put(jaddress);
|
||||
}
|
||||
return jaddresses.toString();
|
||||
}
|
||||
|
||||
static Address[] decodeAddresses(String json) {
|
||||
List<Address> result = new ArrayList<>();
|
||||
try {
|
||||
JSONArray jaddresses = new JSONArray(json);
|
||||
for (int i = 0; i < jaddresses.length(); i++) {
|
||||
JSONObject jaddress = (JSONObject) jaddresses.get(i);
|
||||
if (jaddress.has("personal"))
|
||||
result.add(new InternetAddress(
|
||||
jaddress.getString("address"),
|
||||
jaddress.getString("personal")));
|
||||
else
|
||||
result.add(new InternetAddress(
|
||||
jaddress.getString("address")));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
return result.toArray(new Address[0]);
|
||||
}
|
||||
|
||||
String getHtml() throws MessagingException {
|
||||
return getHtml(imessage);
|
||||
}
|
||||
|
||||
static String getFormattedAddresses(String json) {
|
||||
try {
|
||||
List<String> addresses = new ArrayList<>();
|
||||
for (Address address : decodeAddresses(json))
|
||||
if (address instanceof InternetAddress) {
|
||||
InternetAddress a = (InternetAddress) address;
|
||||
String personal = a.getPersonal();
|
||||
if (TextUtils.isEmpty(personal))
|
||||
addresses.add(address.toString());
|
||||
else
|
||||
addresses.add(personal);
|
||||
} else
|
||||
addresses.add(address.toString());
|
||||
return TextUtils.join(", ", addresses);
|
||||
} catch (Throwable ex) {
|
||||
return ex.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private String getHtml(Part part) throws MessagingException {
|
||||
if (part.isMimeType("text/*"))
|
||||
try {
|
||||
String s = (String) part.getContent();
|
||||
if (part.isMimeType("text/plain"))
|
||||
s = "<pre>" + s.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
return s;
|
||||
} catch (IOException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (part.isMimeType("multipart/alternative")) {
|
||||
String text = null;
|
||||
try {
|
||||
Multipart mp = (Multipart) part.getContent();
|
||||
for (int i = 0; i < mp.getCount(); i++) {
|
||||
Part bp = mp.getBodyPart(i);
|
||||
if (bp.isMimeType("text/plain")) {
|
||||
if (text == null)
|
||||
text = getHtml(bp);
|
||||
} else if (bp.isMimeType("text/html")) {
|
||||
String s = getHtml(bp);
|
||||
if (s != null)
|
||||
return s;
|
||||
} else
|
||||
return getHtml(bp);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
if (part.isMimeType("multipart/*")) {
|
||||
try {
|
||||
Multipart mp = (Multipart) part.getContent();
|
||||
for (int i = 0; i < mp.getCount(); i++) {
|
||||
String s = getHtml(mp.getBodyPart(i));
|
||||
if (s != null)
|
||||
return s;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean getSeen() throws MessagingException {
|
||||
return imessage.isSet(Flags.Flag.SEEN);
|
||||
}
|
||||
|
||||
String getRaw() throws IOException, MessagingException {
|
||||
if (raw == null) {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
imessage.writeTo(os);
|
||||
raw = Base64.encodeToString(os.toByteArray(), Base64.URL_SAFE);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Provider {
|
||||
public String name;
|
||||
public String imap_host;
|
||||
public int imap_port;
|
||||
public String smtp_host;
|
||||
public int smtp_port;
|
||||
public boolean starttls;
|
||||
|
||||
private Provider() {
|
||||
}
|
||||
|
||||
Provider(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
static List<Provider> loadProfiles(Context context) {
|
||||
List<Provider> result = null;
|
||||
try {
|
||||
XmlResourceParser xml = context.getResources().getXml(R.xml.providers);
|
||||
int eventType = xml.getEventType();
|
||||
Provider provider = null;
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if ("providers".equals(xml.getName()))
|
||||
result = new ArrayList<>();
|
||||
else if ("provider".equals(xml.getName())) {
|
||||
provider = new Provider();
|
||||
provider.name = xml.getAttributeValue(null, "name");
|
||||
} else if ("imap".equals(xml.getName())) {
|
||||
provider.imap_host = xml.getAttributeValue(null, "host");
|
||||
provider.imap_port = xml.getAttributeIntValue(null, "port", 0);
|
||||
} else if ("smtp".equals(xml.getName())) {
|
||||
provider.smtp_host = xml.getAttributeValue(null, "host");
|
||||
provider.smtp_port = xml.getAttributeIntValue(null, "port", 0);
|
||||
provider.starttls = xml.getAttributeBooleanValue(null, "starttls", false);
|
||||
} else
|
||||
throw new IllegalAccessException(xml.getName());
|
||||
} else if (eventType == XmlPullParser.END_TAG) {
|
||||
if ("provider".equals(xml.getName())) {
|
||||
result.add(provider);
|
||||
provider = null;
|
||||
}
|
||||
}
|
||||
|
||||
eventType = xml.next();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class ReceiverAutostart extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) ||
|
||||
Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction()))
|
||||
ServiceSynchronize.start(context);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class TupleAccountStats {
|
||||
public Integer accounts;
|
||||
public Integer operations;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class TupleFolderEx extends EntityFolder {
|
||||
public String accountName;
|
||||
public int messages;
|
||||
public int unseen;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof TupleFolderEx) {
|
||||
TupleFolderEx other = (TupleFolderEx) obj;
|
||||
return (super.equals(obj) &&
|
||||
this.accountName == null ? other.accountName == null : accountName.equals(other.accountName) &&
|
||||
this.messages == other.messages &&
|
||||
this.unseen == other.unseen);
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class TupleMessageEx extends EntityMessage {
|
||||
public String folderName;
|
||||
public String folderType;
|
||||
public int count;
|
||||
public int unseen;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof TupleMessageEx) {
|
||||
TupleMessageEx other = (TupleMessageEx) obj;
|
||||
return (super.equals(obj) &&
|
||||
this.folderType.equals(other.folderType) &&
|
||||
this.count == other.count &&
|
||||
this.unseen == other.unseen);
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class TupleOperationEx extends EntityOperation {
|
||||
public Long uid;
|
||||
}
|
After Width: | Height: | Size: 146 B |
After Width: | Height: | Size: 136 B |
After Width: | Height: | Size: 159 B |
After Width: | Height: | Size: 114 B |
After Width: | Height: | Size: 150 B |
After Width: | Height: | Size: 138 B |
After Width: | Height: | Size: 158 B |
After Width: | Height: | Size: 97 B |
After Width: | Height: | Size: 232 B |
After Width: | Height: | Size: 240 B |
After Width: | Height: | Size: 346 B |
After Width: | Height: | Size: 369 B |
After Width: | Height: | Size: 235 B |
After Width: | Height: | Size: 236 B |
After Width: | Height: | Size: 344 B |
After Width: | Height: | Size: 366 B |
After Width: | Height: | Size: 165 B |
After Width: | Height: | Size: 173 B |
After Width: | Height: | Size: 194 B |
After Width: | Height: | Size: 230 B |
After Width: | Height: | Size: 169 B |
After Width: | Height: | Size: 176 B |
After Width: | Height: | Size: 195 B |
After Width: | Height: | Size: 222 B |
After Width: | Height: | Size: 193 B |
After Width: | Height: | Size: 242 B |
After Width: | Height: | Size: 304 B |
After Width: | Height: | Size: 299 B |
After Width: | Height: | Size: 194 B |
After Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 305 B |
After Width: | Height: | Size: 297 B |
After Width: | Height: | Size: 160 B |
After Width: | Height: | Size: 146 B |
After Width: | Height: | Size: 187 B |
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 160 B |
After Width: | Height: | Size: 140 B |
After Width: | Height: | Size: 179 B |
After Width: | Height: | Size: 243 B |
After Width: | Height: | Size: 147 B |
After Width: | Height: | Size: 149 B |