Compare commits
76 Commits
2.5.0.0bet
...
2.5.0.0bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1033dd78b0 | ||
|
|
4aaae3f59e | ||
|
|
1424633a58 | ||
|
|
c9b98094e5 | ||
|
|
5707026985 | ||
|
|
bfcfb842fb | ||
|
|
8fe2230891 | ||
|
|
41cc0b1a5a | ||
|
|
cc8c525dab | ||
|
|
92bc3c2838 | ||
|
|
1d065e7bc5 | ||
|
|
fb1b90a281 | ||
|
|
268f716104 | ||
|
|
b7328875f1 | ||
|
|
02ee58efa7 | ||
|
|
6754881847 | ||
|
|
6e4c5d8c26 | ||
|
|
544648c2eb | ||
|
|
e62f8bf56b | ||
|
|
7f5138b08b | ||
|
|
a367aeaf12 | ||
|
|
fc6453beba | ||
|
|
8a981b7a79 | ||
|
|
d7f9a02699 | ||
|
|
7c9153ea04 | ||
|
|
2c16fe3335 | ||
|
|
eb14d27ca5 | ||
|
|
7024178069 | ||
|
|
c85ce3e0e8 | ||
|
|
067c5ff1cf | ||
|
|
429bd99f1c | ||
|
|
7b5bb0fd97 | ||
|
|
ae50f424d3 | ||
|
|
53cc1ad673 | ||
|
|
ceb80c6cac | ||
|
|
71bd3ab780 | ||
|
|
8a40a4b3ae | ||
|
|
7772db17ae | ||
|
|
11738d807c | ||
|
|
80b9a8fa50 | ||
|
|
d619f6581e | ||
|
|
697e9aa923 | ||
|
|
15c843fbb9 | ||
|
|
b6b7e61cfb | ||
|
|
d5b0ee9371 | ||
|
|
e86807c3d3 | ||
|
|
08fcb119a9 | ||
|
|
edff33afd1 | ||
|
|
bdfa963bed | ||
|
|
4eff247865 | ||
|
|
3e3d5bd7d8 | ||
|
|
5a74d1be8f | ||
|
|
812eccfe0e | ||
|
|
d00c337382 | ||
|
|
54dbcc95ab | ||
|
|
2e5ddd80ff | ||
|
|
2c44a4d760 | ||
|
|
f8561222d5 | ||
|
|
2c8e3e9c8e | ||
|
|
ccbc6c07ed | ||
|
|
47e820e3d8 | ||
|
|
13dea4b567 | ||
|
|
70d1ce715e | ||
|
|
cc41545c0a | ||
|
|
aded8fab0a | ||
|
|
9cb4d28ad8 | ||
|
|
a1b2235fb3 | ||
|
|
ed7f6e4b68 | ||
|
|
1ebba9d8be | ||
|
|
347522b9a6 | ||
|
|
918f563faa | ||
|
|
041f3eb568 | ||
|
|
4a1d97a622 | ||
|
|
cd0d712f56 | ||
|
|
761d9a1883 | ||
|
|
2c9a6d7c26 |
@@ -1,3 +1,12 @@
|
||||
KeepassDX (2.5.0.0beta9)
|
||||
* Education Screens to learn how to use the app
|
||||
* New designs
|
||||
* New custom font for character visibility
|
||||
* New themes
|
||||
* New icon pack
|
||||
* Change setting organisation
|
||||
* Pro version
|
||||
|
||||
KeepassDX (2.5.0.0beta8)
|
||||
* Hide custom entries protected
|
||||
* Best management of field references (https://keepass.info/help/base/fieldrefs.html)
|
||||
|
||||
396
LICENSES/LICENSE-FONT-AWESOME
Normal file
@@ -0,0 +1,396 @@
|
||||
Attribution 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
|
||||
201
LICENSES/LICENSE_FONT_DROID_SANS_MONO.txt
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
46
ReadMe.md
@@ -1,37 +1,45 @@
|
||||
# Android Keepass DX
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/icon.png"> Keepass DX is a material design Keepass Client for manage keys and passwords in crypt database for your android device.
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> Keepass DX is a multi-format KeePass manager for Android devices. The application allows to create keys and passwords in a secure way by integrating with the Android design standards.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
|
||||
|
||||
### Features
|
||||
|
||||
* Create database files / entries and groups
|
||||
* Support for .kdb and .kdbx files (version 1 to 4)
|
||||
* Open database, copy username / password, open URI / URL
|
||||
* Fingerprint for fast unlocking
|
||||
* Material design with themes
|
||||
* AutoFill and Integration (Development in progress)
|
||||
* Precise management of settings
|
||||
* Create database files / entries and groups
|
||||
* Support for .kdb and .kdbx files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm
|
||||
* Compatible with the majority of alternative programs (KeePass, KeePassX, KeePass XC...)
|
||||
* Allows fast copy of fields and opening of URI / URL
|
||||
* Fingerprint for fast unlocking
|
||||
* Material design with themes
|
||||
* AutoFill and Integration
|
||||
* Precise management of settings
|
||||
|
||||
Keepass DX is opensource and ad-free.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen1.jpg" width="220">
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen2.jpg" width="220">
|
||||
|
||||
## What is KeePass?
|
||||
## What is KeePass DX?
|
||||
|
||||
Today you need to remember many passwords. You need a password for the Windows network logon, your e-mail account, your website's FTP password, online passwords (like website member account), etc. etc. etc. The list is endless. Also, you should use different passwords for each account. Because if you use only one password everywhere and someone gets this password you have a problem... A serious problem. The thief would have access to your e-mail account, website, etc. Unimaginable.
|
||||
Today you need to remember many passwords. You need a password for your e-mail account, your website's FTP password, online passwords (like website member account), etc. etc. etc. The list is endless. Also, you should use different passwords for each account. Because if you use only one password everywhere and someone gets this password you have a problem... A serious problem. The thief would have access to your e-mail account, website, etc. Unimaginable.
|
||||
|
||||
KeePass is a free open source password manager, which helps you to manage your passwords in a secure way. You can put all your passwords in one database, which is locked with one master key or a key file. So you only have to remember one single master password or select the key file to unlock the whole database. The databases are encrypted using the best and most secure encryption algorithms currently known (AES and Twofish). For more information, see the features page.
|
||||
KeePass DX is a free open source password manager for Android, which helps you to manage your passwords in a secure way. You can put all your passwords in one database, which is locked with one master key or a key file. So you only have to remember one single master password or select the key file to unlock the whole database. The databases are encrypted using the best and most secure encryption algorithms currently known.
|
||||
|
||||
## Is it really free?
|
||||
|
||||
Yes, KeePass is really free, and more than that: it is open source (OSI certified). You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
|
||||
Yes, KeePass DX is under free license (OSI certified) and without advertising. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
|
||||
|
||||
## Donation
|
||||
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free.*
|
||||
|
||||
Even if the application is free, to maintain the application, you can make donations.
|
||||
## Contributions
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KM6QMDAXZM3UU "Kunzisoft Paypal Donation")
|
||||
You can contribute in different ways to help us on our work.
|
||||
|
||||
[](https://liberapay.com/Kunzisoft/donate "Kunzisoft Liberapay Donation")
|
||||
* Add features by a **pull request**.
|
||||
* Help to **translate** into your language
|
||||
* **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features.
|
||||
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePass DX
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.jpg" width="220">
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen5.jpg" width="220">
|
||||
@@ -45,6 +53,12 @@ Even if the application is free, to maintain the application, you can make donat
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Get it on Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
|
||||
|
||||
## Other devices
|
||||
|
||||
- [KeePass XC](https://keepassxc.org/) (https://keepassxc.org/) works with **GNU/Linux**, **Mac** and **Windows**, is updated regulary and under the terms of the GNU General Public License. It's the recommended version for computers.
|
||||
|
||||
- [KeePass](https://keepass.info/) (https://keepass.info/) is the historical project, with good technical documentation for standardized database files but only running on **Windows**.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion = 27
|
||||
buildToolsVersion = '27.0.3'
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '27.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 27
|
||||
versionCode = 8
|
||||
versionName = "2.5.0.0beta8"
|
||||
versionCode = 9
|
||||
versionName = "2.5.0.0beta9"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
@@ -19,6 +19,8 @@ android {
|
||||
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
|
||||
'arm64-v8a', 'mips', 'mips64'
|
||||
}
|
||||
|
||||
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
@@ -44,28 +46,42 @@ android {
|
||||
applicationIdSuffix = ".libre"
|
||||
versionNameSuffix "-libre"
|
||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||
buildConfigField "boolean", "GOOGLE_PLAY_VERSION", "false"
|
||||
buildConfigField "boolean", "CLOSED_STORE", "false"
|
||||
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Purple\"}"
|
||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
||||
|
||||
}
|
||||
pro_google {
|
||||
pro {
|
||||
applicationIdSuffix = ".pro"
|
||||
versionNameSuffix "-pro"
|
||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||
buildConfigField "boolean", "GOOGLE_PLAY_VERSION", "true"
|
||||
buildConfigField "boolean", "CLOSED_STORE", "true"
|
||||
buildConfigField "String[]", "STYLES_DISABLED", "{}"
|
||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
||||
}
|
||||
free_google {
|
||||
free {
|
||||
applicationIdSuffix = ".free"
|
||||
versionNameSuffix "-free"
|
||||
buildConfigField "boolean", "FULL_VERSION", "false"
|
||||
buildConfigField "boolean", "GOOGLE_PLAY_VERSION", "true"
|
||||
buildConfigField "boolean", "CLOSED_STORE", "true"
|
||||
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Purple\"}"
|
||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{\"material\"}"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
libre.res.srcDir 'src/libre/res'
|
||||
pro.res.srcDir 'src/pro/res'
|
||||
free.res.srcDir 'src/free/res'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility 1.8
|
||||
sourceCompatibility 1.8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
def supportVersion = "27.1.0"
|
||||
def supportVersion = "27.1.1"
|
||||
def spongycastleVersion = "1.58.0.0"
|
||||
def permissionDispatcherVersion = "3.1.0"
|
||||
|
||||
@@ -77,20 +93,26 @@ dependencies {
|
||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
|
||||
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
|
||||
// Expandable view
|
||||
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
|
||||
// Time
|
||||
implementation "joda-time:joda-time:2.9.9"
|
||||
implementation "org.sufficientlysecure:html-textview:3.5"
|
||||
implementation "com.nononsenseapps:filepicker:4.1.0"
|
||||
implementation 'joda-time:joda-time:2.9.9'
|
||||
implementation 'org.sufficientlysecure:html-textview:3.5'
|
||||
implementation 'com.nononsenseapps:filepicker:4.1.0'
|
||||
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.11.0'
|
||||
// Permissions
|
||||
implementation ("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
|
||||
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
|
||||
// if you don't use android.app.Fragment you can exclude support for them
|
||||
exclude module: "support-v13"
|
||||
}
|
||||
// Apache Commons Collections
|
||||
implementation group: 'commons-collections', name: 'commons-collections', version: '3.2.1'
|
||||
// Base64
|
||||
compile group: 'biz.source_code', name: 'base64coder', version: '2010-12-19'
|
||||
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.1'
|
||||
implementation group: 'com.google.guava', name: 'guava', version: '23.0-android'
|
||||
// Apache Commons Collections
|
||||
implementation 'commons-collections:commons-collections:3.2.1'
|
||||
// Base64
|
||||
implementation 'biz.source_code:base64coder:2010-12-19'
|
||||
implementation 'com.google.code.gson:gson:2.8.1'
|
||||
implementation 'com.google.guava:guava:23.0-android'
|
||||
// Icon pack, classic for all, material for libre and pro
|
||||
implementation project(path: ':icon-pack-classic')
|
||||
implementation project(path: ':icon-pack-material')
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class DeleteEntry extends AndroidTestCase {
|
||||
assertNotNull("Could not find group1", group1);
|
||||
|
||||
// Delete the group
|
||||
DeleteGroup task = new DeleteGroup(db, group1, null, true);
|
||||
DeleteGroup task = new DeleteGroup(null, db, group1, null, true);
|
||||
task.run();
|
||||
|
||||
// Verify the entries were deleted
|
||||
|
||||
BIN
app/src/free/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/free/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/free/res/mipmap-ldpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/free/res/mipmap-ldpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/free/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/free/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/free/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
app/src/free/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
app/src/free/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
app/src/free/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/free/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/free/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/src/libre/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/libre/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
app/src/libre/res/mipmap-ldpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/libre/res/mipmap-ldpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/libre/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/libre/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/libre/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
app/src/libre/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
app/src/libre/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
app/src/libre/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/libre/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/libre/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -20,47 +20,28 @@
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup"
|
||||
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
||||
android:theme="@style/KeepassDXStyle.Light"
|
||||
android:theme="@style/KeepassDXStyle.Night"
|
||||
tools:replace="android:theme">
|
||||
<!-- TODO backup API Key -->
|
||||
<meta-data
|
||||
android:name="com.google.android.backup.api_key"
|
||||
android:value="" />
|
||||
|
||||
<!-- Folder picker -->
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/nnf_provider_paths" />
|
||||
</provider>
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.fileselect.FilePickerStylishActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".KeePass"
|
||||
android:label="@string/app_name">
|
||||
android:name="com.kunzisoft.keepass.fileselect.FileSelectActivity"
|
||||
android:theme="@style/KeepassDXStyle.SplashScreen"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="stateHidden" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.fileselect.FileSelectActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.AboutActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/menu_about" />
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.password.PasswordActivity"
|
||||
@@ -102,6 +83,24 @@
|
||||
<data android:mimeType="application/octet-stream"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Folder picker -->
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/nnf_provider_paths" />
|
||||
</provider>
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.fileselect.FilePickerStylishActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.GroupActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
@@ -154,4 +153,4 @@
|
||||
</service>
|
||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
BIN
app/src/main/assets/fonts/DroidSansMonoSlashed.ttf
Normal file
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.kunzisoft.keepass.fileselect.FileSelectActivity;
|
||||
|
||||
public class KeePass extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
startFileSelectActivity();
|
||||
// Delete flickering for kitkat <=
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
protected void startFileSelectActivity() {
|
||||
FileSelectActivity.launch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,11 @@ package com.kunzisoft.keepass.activities;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
@@ -34,6 +37,8 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
import com.kunzisoft.keepass.database.Database;
|
||||
@@ -41,9 +46,11 @@ import com.kunzisoft.keepass.database.ExtraFields;
|
||||
import com.kunzisoft.keepass.database.PwDatabase;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
import com.kunzisoft.keepass.notifications.NotificationCopyingService;
|
||||
import com.kunzisoft.keepass.notifications.NotificationField;
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||
import com.kunzisoft.keepass.settings.SettingsAutofillActivity;
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper;
|
||||
import com.kunzisoft.keepass.utils.EmptyUtils;
|
||||
import com.kunzisoft.keepass.utils.MenuUtil;
|
||||
@@ -65,6 +72,7 @@ public class EntryActivity extends LockingHideActivity {
|
||||
private ImageView titleIconView;
|
||||
private TextView titleView;
|
||||
private EntryContentsView entryContentsView;
|
||||
private Toolbar toolbar;
|
||||
|
||||
protected PwEntry mEntry;
|
||||
private boolean mShowPassword;
|
||||
@@ -87,7 +95,7 @@ public class EntryActivity extends LockingHideActivity {
|
||||
|
||||
setContentView(R.layout.entry_view);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
|
||||
@@ -202,9 +210,73 @@ public class EntryActivity extends LockingHideActivity {
|
||||
firstLaunchOfActivity = false;
|
||||
}
|
||||
|
||||
private void populateTitle(Drawable drawIcon, String text) {
|
||||
titleIconView.setImageDrawable(drawIcon);
|
||||
titleView.setText(text);
|
||||
/**
|
||||
* Check and display learning views
|
||||
* Displays the explanation for copying a field and editing an entry
|
||||
*/
|
||||
private void checkAndPerformedEducation(Menu menu) {
|
||||
|
||||
if (entryContentsView != null && entryContentsView.isUserNamePresent()
|
||||
&& !PreferencesUtil.isEducationCopyUsernamePerformed(this)) {
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forView(findViewById(R.id.entry_user_name_action_image),
|
||||
getString(R.string.education_field_copy_title),
|
||||
getString(R.string.education_field_copy_summary))
|
||||
.tintTarget(false)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
clipboardHelper.timeoutCopyToClipboard(mEntry.getUsername(),
|
||||
getString(R.string.copy_field, getString(R.string.entry_user_name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
// Launch autofill settings
|
||||
startActivity(new Intent(EntryActivity.this, SettingsAutofillActivity.class));
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(this,
|
||||
R.string.education_copy_username_key);
|
||||
|
||||
} else if (!PreferencesUtil.isEducationEntryEditPerformed(this)) {
|
||||
|
||||
try {
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forToolbarMenuItem(toolbar, R.id.menu_edit,
|
||||
getString(R.string.education_entry_edit_title),
|
||||
getString(R.string.education_entry_edit_summary))
|
||||
.tintTarget(true)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
MenuItem editItem = menu.findItem(R.id.menu_edit);
|
||||
onOptionsItemSelected(editItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
// Open Keepass doc to create field references
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse(getString(R.string.field_references_url)));
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(this,
|
||||
R.string.education_entry_edit_key);
|
||||
} catch (Exception e) {
|
||||
// If icon not visible
|
||||
Log.w(TAG, "Can't performed education for entry's edition");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void fillData() {
|
||||
@@ -213,9 +285,19 @@ public class EntryActivity extends LockingHideActivity {
|
||||
|
||||
mEntry.startToManageFieldReferences(pm);
|
||||
|
||||
// Assign title
|
||||
populateTitle(db.getDrawFactory().getIconDrawable(getResources(), mEntry.getIcon()),
|
||||
mEntry.getTitle());
|
||||
// Assign title icon
|
||||
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
|
||||
// Retrieve the textColor to tint the icon
|
||||
int[] attrs = {R.attr.textColorInverse};
|
||||
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
||||
int iconColor = ta.getColor(0, Color.WHITE);
|
||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), true, iconColor);
|
||||
} else {
|
||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon());
|
||||
}
|
||||
|
||||
// Assign title text
|
||||
titleView.setText(mEntry.getTitle());
|
||||
|
||||
// Assign basic fields
|
||||
entryContentsView.assignUserName(mEntry.getUsername());
|
||||
@@ -323,6 +405,9 @@ public class EntryActivity extends LockingHideActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show education views
|
||||
new Handler().post(() -> checkAndPerformedEducation(menu));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ package com.kunzisoft.keepass.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
@@ -31,11 +33,13 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
import com.kunzisoft.keepass.database.Database;
|
||||
@@ -52,7 +56,7 @@ import com.kunzisoft.keepass.database.edit.UpdateEntry;
|
||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||
import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
||||
import com.kunzisoft.keepass.icons.Icons;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||
import com.kunzisoft.keepass.tasks.ProgressTask;
|
||||
import com.kunzisoft.keepass.utils.MenuUtil;
|
||||
@@ -62,6 +66,8 @@ import com.kunzisoft.keepass.view.EntryEditCustomField;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.UNDEFINED_ICON_ID;
|
||||
|
||||
public class EntryEditActivity extends LockingHideActivity
|
||||
implements IconPickerDialogFragment.IconPickerListener,
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener {
|
||||
@@ -81,20 +87,26 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
protected PwEntry mEntry;
|
||||
protected PwEntry mCallbackNewEntry;
|
||||
protected boolean mIsNew;
|
||||
protected int mSelectedIconID = -1;
|
||||
protected int mSelectedIconID = UNDEFINED_ICON_ID;
|
||||
|
||||
// Views
|
||||
private ScrollView scrollView;
|
||||
private EditText entryTitleView;
|
||||
private ImageView entryIconView;
|
||||
private EditText entryUserNameView;
|
||||
private EditText entryUrlView;
|
||||
private EditText entryPasswordView;
|
||||
private EditText entryConfirmationPasswordView;
|
||||
private View generatePasswordView;
|
||||
private EditText entryCommentView;
|
||||
private ViewGroup entryExtraFieldsContainer;
|
||||
private View addNewFieldView;
|
||||
private View saveView;
|
||||
private int iconColor;
|
||||
|
||||
/**
|
||||
* launch EntryEditActivity to update an existing entry
|
||||
* Launch EntryEditActivity to update an existing entry
|
||||
*
|
||||
* @param act from activity
|
||||
* @param pw Entry to update
|
||||
*/
|
||||
@@ -107,7 +119,8 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
|
||||
/**
|
||||
* launch EntryEditActivity to add a new entry
|
||||
* Launch EntryEditActivity to add a new entry
|
||||
*
|
||||
* @param act from activity
|
||||
* @param pwGroup Group who will contains new entry
|
||||
*/
|
||||
@@ -135,6 +148,7 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
|
||||
|
||||
entryTitleView = findViewById(R.id.entry_title);
|
||||
entryIconView = findViewById(R.id.icon_button);
|
||||
entryUserNameView = findViewById(R.id.entry_user_name);
|
||||
entryUrlView = findViewById(R.id.entry_url);
|
||||
entryPasswordView = findViewById(R.id.entry_password);
|
||||
@@ -152,12 +166,23 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
Intent intent = getIntent();
|
||||
byte[] uuidBytes = intent.getByteArrayExtra(KEY_ENTRY);
|
||||
|
||||
// Retrieve the textColor to tint the icon
|
||||
int[] attrs = {android.R.attr.textColorPrimary};
|
||||
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
||||
iconColor = ta.getColor(0, Color.WHITE);
|
||||
|
||||
PwDatabase pm = db.getPwDatabase();
|
||||
if ( uuidBytes == null ) {
|
||||
PwGroupId parentId = (PwGroupId) intent.getSerializableExtra(KEY_PARENT);
|
||||
PwGroup parent = pm.getGroupByGroupId(parentId);
|
||||
mEntry = PwEntry.getInstance(parent);
|
||||
mEntry = db.createEntry(parent);
|
||||
mIsNew = true;
|
||||
// Add the default icon
|
||||
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
|
||||
App.getDB().getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, true, iconColor);
|
||||
} else {
|
||||
App.getDB().getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView);
|
||||
}
|
||||
} else {
|
||||
UUID uuid = Types.bytestoUUID(uuidBytes);
|
||||
mEntry = pm.getEntryByUUIDId(uuid);
|
||||
@@ -165,81 +190,199 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
fillData();
|
||||
}
|
||||
|
||||
View iconButton = findViewById(R.id.icon_button);
|
||||
iconButton.setOnClickListener(v ->
|
||||
// Retrieve the icon after an orientation change
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(IconPickerDialogFragment.KEY_ICON_ID)) {
|
||||
iconPicked(savedInstanceState);
|
||||
}
|
||||
|
||||
// Add listener to the icon
|
||||
entryIconView.setOnClickListener(v ->
|
||||
IconPickerDialogFragment.launch(EntryEditActivity.this));
|
||||
|
||||
// Generate password button
|
||||
View generatePassword = findViewById(R.id.generate_button);
|
||||
generatePassword.setOnClickListener(v -> {
|
||||
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
|
||||
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
|
||||
});
|
||||
generatePasswordView = findViewById(R.id.generate_button);
|
||||
generatePasswordView.setOnClickListener(v -> openPasswordGenerator());
|
||||
|
||||
// Save button
|
||||
View save = findViewById(R.id.entry_save);
|
||||
save.setOnClickListener(v -> {
|
||||
if (!validateBeforeSaving()) {
|
||||
return;
|
||||
}
|
||||
mCallbackNewEntry = populateNewEntry();
|
||||
|
||||
OnFinish onFinish = new AfterSave();
|
||||
EntryEditActivity act = EntryEditActivity.this;
|
||||
RunnableOnFinish task;
|
||||
if ( mIsNew ) {
|
||||
task = new AddEntry(act, App.getDB(), mCallbackNewEntry, onFinish);
|
||||
} else {
|
||||
task = new UpdateEntry(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
|
||||
}
|
||||
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
|
||||
pt.run();
|
||||
});
|
||||
saveView = findViewById(R.id.entry_save);
|
||||
saveView.setOnClickListener(v -> saveEntry());
|
||||
|
||||
|
||||
if (mEntry.allowExtraFields()) {
|
||||
View add = findViewById(R.id.add_new_field);
|
||||
add.setVisibility(View.VISIBLE);
|
||||
add.setOnClickListener(v -> {
|
||||
EntryEditCustomField ees = new EntryEditCustomField(EntryEditActivity.this);
|
||||
ees.setData("", new ProtectedString(false, ""));
|
||||
entryExtraFieldsContainer.addView(ees);
|
||||
|
||||
// Scroll bottom
|
||||
scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
|
||||
});
|
||||
addNewFieldView = findViewById(R.id.add_new_field);
|
||||
addNewFieldView.setVisibility(View.VISIBLE);
|
||||
addNewFieldView.setOnClickListener(v -> addNewCustomField());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean validateBeforeSaving() {
|
||||
// Require title
|
||||
String title = entryTitleView.getText().toString();
|
||||
if ( title.length() == 0 ) {
|
||||
Toast.makeText(this, R.string.error_title_required, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate password
|
||||
String pass = entryPasswordView.getText().toString();
|
||||
String conf = entryConfirmationPasswordView.getText().toString();
|
||||
if ( ! pass.equals(conf) ) {
|
||||
Toast.makeText(this, R.string.error_pass_match, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate extra fields
|
||||
// Verify the education views
|
||||
checkAndPerformedEducation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the password generator fragment
|
||||
*/
|
||||
private void openPasswordGenerator() {
|
||||
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
|
||||
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new view to fill in the information of the customized field
|
||||
*/
|
||||
private void addNewCustomField() {
|
||||
EntryEditCustomField entryEditCustomField = new EntryEditCustomField(EntryEditActivity.this);
|
||||
entryEditCustomField.setData("", new ProtectedString(false, ""));
|
||||
boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this);
|
||||
entryEditCustomField.setFontVisibility(visibilityFontActivated);
|
||||
entryExtraFieldsContainer.addView(entryEditCustomField);
|
||||
|
||||
// Scroll bottom
|
||||
scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the new entry or update an existing entry in the database
|
||||
*/
|
||||
private void saveEntry() {
|
||||
if (!validateBeforeSaving()) {
|
||||
return;
|
||||
}
|
||||
mCallbackNewEntry = populateNewEntry();
|
||||
|
||||
OnFinish onFinish = new AfterSave();
|
||||
EntryEditActivity act = EntryEditActivity.this;
|
||||
RunnableOnFinish task;
|
||||
if ( mIsNew ) {
|
||||
task = new AddEntry(act, App.getDB(), mCallbackNewEntry, onFinish);
|
||||
} else {
|
||||
task = new UpdateEntry(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
|
||||
}
|
||||
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and display learning views
|
||||
* Displays the explanation for the icon selection, the password generator and for a new field
|
||||
*/
|
||||
private void checkAndPerformedEducation() {
|
||||
|
||||
// TODO Show icon
|
||||
|
||||
if (!PreferencesUtil.isEducationPasswordGeneratorPerformed(this)) {
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forView(generatePasswordView,
|
||||
getString(R.string.education_generate_password_title),
|
||||
getString(R.string.education_generate_password_summary))
|
||||
.tintTarget(false)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
openPasswordGenerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(this,
|
||||
R.string.education_password_generator_key);
|
||||
}
|
||||
|
||||
else if (mEntry.allowExtraFields()
|
||||
&& !mEntry.containsCustomFields()
|
||||
&& !PreferencesUtil.isEducationEntryNewFieldPerformed(this)) {
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forView(addNewFieldView,
|
||||
getString(R.string.education_entry_new_field_title),
|
||||
getString(R.string.education_entry_new_field_summary))
|
||||
.tintTarget(false)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
addNewCustomField();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(this,
|
||||
R.string.education_entry_new_field_key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to retrieve a validation or an error with a message
|
||||
*/
|
||||
private class ErrorValidation {
|
||||
static final int unknownMessage = -1;
|
||||
|
||||
boolean isValidate = false;
|
||||
int messageId = unknownMessage;
|
||||
|
||||
void showValidationErrorIfNeeded() {
|
||||
if (!isValidate && messageId != unknownMessage)
|
||||
Toast.makeText(EntryEditActivity.this, messageId, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate or not the entry form
|
||||
*
|
||||
* @return ErrorValidation An error with a message or a validation without message
|
||||
*/
|
||||
protected ErrorValidation validate() {
|
||||
ErrorValidation errorValidation = new ErrorValidation();
|
||||
|
||||
// Require title
|
||||
String title = entryTitleView.getText().toString();
|
||||
if ( title.length() == 0 ) {
|
||||
errorValidation.messageId = R.string.error_title_required;
|
||||
return errorValidation;
|
||||
}
|
||||
|
||||
// Validate password
|
||||
String pass = entryPasswordView.getText().toString();
|
||||
String conf = entryConfirmationPasswordView.getText().toString();
|
||||
if ( ! pass.equals(conf) ) {
|
||||
errorValidation.messageId = R.string.error_pass_match;
|
||||
return errorValidation;
|
||||
}
|
||||
|
||||
// Validate extra fields
|
||||
if (mEntry.allowExtraFields()) {
|
||||
for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
|
||||
EntryEditCustomField entryEditCustomField = (EntryEditCustomField) entryExtraFieldsContainer.getChildAt(i);
|
||||
String key = entryEditCustomField.getLabel();
|
||||
if (key == null || key.length() == 0) {
|
||||
Toast.makeText(this, R.string.error_string_key, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
errorValidation.messageId = R.string.error_string_key;
|
||||
return errorValidation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
errorValidation.isValidate = true;
|
||||
return errorValidation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a validation with {@link #validate()} and show the error if present
|
||||
*
|
||||
* @return true if the form was validate or false if not
|
||||
*/
|
||||
protected boolean validateBeforeSaving() {
|
||||
ErrorValidation errorValidation = validate();
|
||||
errorValidation.showValidationErrorIfNeeded();
|
||||
return errorValidation.isValidate;
|
||||
}
|
||||
|
||||
protected PwEntry populateNewEntry() {
|
||||
@@ -255,18 +398,8 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
newEntry.setLastModificationTime(new PwDate());
|
||||
|
||||
newEntry.setTitle(entryTitleView.getText().toString());
|
||||
if(mSelectedIconID != -1)
|
||||
// or TODO icon factory newEntry.setIcon(App.getDB().pm.iconFactory.getIcon(mSelectedIconID));
|
||||
newEntry.setIcon(new PwIconStandard(mSelectedIconID));
|
||||
else {
|
||||
if (mIsNew) {
|
||||
newEntry.setIcon(App.getDB().getPwDatabase().getIconFactory().getFirstIcon());
|
||||
}
|
||||
else {
|
||||
// Keep previous icon, if no new one was selected
|
||||
newEntry.setIcon(mEntry.getIconStandard());
|
||||
}
|
||||
}
|
||||
newEntry.setIcon(retrieveIcon());
|
||||
|
||||
newEntry.setUrl(entryUrlView.getText().toString());
|
||||
newEntry.setUsername(entryUserNameView.getText().toString());
|
||||
newEntry.setNotes(entryCommentView.getText().toString());
|
||||
@@ -290,6 +423,24 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one
|
||||
* @return
|
||||
*/
|
||||
private PwIconStandard retrieveIcon() {
|
||||
if(mSelectedIconID != UNDEFINED_ICON_ID)
|
||||
return App.getDB().getPwDatabase().getIconFactory().getIcon(mSelectedIconID);
|
||||
else {
|
||||
if (mIsNew) {
|
||||
return App.getDB().getPwDatabase().getIconFactory().getFirstIcon();
|
||||
}
|
||||
else {
|
||||
// Keep previous icon, if no new one was selected
|
||||
return mEntry.getIconStandard();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
@@ -314,8 +465,12 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
|
||||
protected void fillData() {
|
||||
ImageButton currIconButton = findViewById(R.id.icon_button);
|
||||
App.getDB().getDrawFactory().assignDrawableTo(currIconButton, getResources(), mEntry.getIcon());
|
||||
|
||||
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
|
||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, entryIconView, mEntry.getIcon(), true, iconColor);
|
||||
} else {
|
||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, entryIconView, mEntry.getIcon());
|
||||
}
|
||||
|
||||
// Don't start the field reference manager, we want to see the raw ref
|
||||
mEntry.endToManageFieldReferences();
|
||||
@@ -330,10 +485,10 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
|
||||
boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this);
|
||||
if (visibilityFontActivated) {
|
||||
Util.applyFontVisibilityTo(entryUserNameView);
|
||||
Util.applyFontVisibilityTo(entryPasswordView);
|
||||
Util.applyFontVisibilityTo(entryConfirmationPasswordView);
|
||||
Util.applyFontVisibilityTo(entryCommentView);
|
||||
Util.applyFontVisibilityTo(this, entryUserNameView);
|
||||
Util.applyFontVisibilityTo(this, entryPasswordView);
|
||||
Util.applyFontVisibilityTo(this, entryConfirmationPasswordView);
|
||||
Util.applyFontVisibilityTo(this, entryCommentView);
|
||||
}
|
||||
|
||||
if (mEntry.allowExtraFields()) {
|
||||
@@ -350,8 +505,15 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
@Override
|
||||
public void iconPicked(Bundle bundle) {
|
||||
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
|
||||
ImageButton currIconButton = findViewById(R.id.icon_button);
|
||||
currIconButton.setImageResource(Icons.iconToResId(mSelectedIconID));
|
||||
entryIconView.setImageResource(IconPackChooser.getSelectedIconPack(this).iconToResId(mSelectedIconID));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
if (mSelectedIconID != UNDEFINED_ICON_ID) {
|
||||
outState.putInt(IconPickerDialogFragment.KEY_ICON_ID, mSelectedIconID);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -359,6 +521,8 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
String generatedPassword = bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID);
|
||||
entryPasswordView.setText(generatedPassword);
|
||||
entryConfirmationPasswordView.setText(generatedPassword);
|
||||
|
||||
checkAndPerformedEducation();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,8 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -41,6 +43,8 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.adapters.NodeAdapter;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
@@ -49,16 +53,21 @@ import com.kunzisoft.keepass.database.Database;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
import com.kunzisoft.keepass.database.PwGroupId;
|
||||
import com.kunzisoft.keepass.database.PwIcon;
|
||||
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||
import com.kunzisoft.keepass.database.PwNode;
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||
import com.kunzisoft.keepass.database.edit.AddGroup;
|
||||
import com.kunzisoft.keepass.database.edit.DeleteEntry;
|
||||
import com.kunzisoft.keepass.database.edit.DeleteGroup;
|
||||
import com.kunzisoft.keepass.database.edit.UpdateGroup;
|
||||
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.ReadOnlyDialog;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
import com.kunzisoft.keepass.search.SearchResultsActivity;
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||
import com.kunzisoft.keepass.tasks.ProgressTask;
|
||||
import com.kunzisoft.keepass.view.AddNodeButtonView;
|
||||
|
||||
@@ -67,19 +76,20 @@ public class GroupActivity extends ListNodesActivity
|
||||
|
||||
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
|
||||
|
||||
private Toolbar toolbar;
|
||||
|
||||
private ImageView iconView;
|
||||
private AddNodeButtonView addNodeButtonView;
|
||||
|
||||
protected boolean addGroupEnabled = false;
|
||||
protected boolean addEntryEnabled = false;
|
||||
protected boolean isRoot = false;
|
||||
protected boolean readOnly = false;
|
||||
protected EditGroupDialogAction editGroupDialogAction = EditGroupDialogAction.NONE;
|
||||
|
||||
private enum EditGroupDialogAction {
|
||||
CREATION, UPDATE, NONE
|
||||
}
|
||||
|
||||
private static final String TAG = "Group Activity:";
|
||||
|
||||
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY";
|
||||
private PwGroup oldGroupToUpdate;
|
||||
|
||||
public static void launch(Activity act) {
|
||||
recordFirstTimeBeforeLaunch(act);
|
||||
@@ -132,9 +142,15 @@ public class GroupActivity extends ListNodesActivity
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY)) {
|
||||
oldGroupToUpdate = (PwGroup) savedInstanceState.getSerializable(OLD_GROUP_TO_UPDATE_KEY);
|
||||
}
|
||||
|
||||
// Construct main view
|
||||
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
|
||||
|
||||
iconView = findViewById(R.id.icon);
|
||||
addNodeButtonView = findViewById(R.id.add_node_button);
|
||||
addNodeButtonView.enableAddGroup(addGroupEnabled);
|
||||
addNodeButtonView.enableAddEntry(addEntryEnabled);
|
||||
@@ -142,7 +158,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
RecyclerView recyclerView = findViewById(R.id.nodes_list);
|
||||
recyclerView.addOnScrollListener(addNodeButtonView.hideButtonOnScrollListener());
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitle("");
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
@@ -150,16 +166,14 @@ public class GroupActivity extends ListNodesActivity
|
||||
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
|
||||
|
||||
addNodeButtonView.setAddGroupClickListener(v -> {
|
||||
editGroupDialogAction = EditGroupDialogAction.CREATION;
|
||||
GroupEditDialogFragment groupEditDialogFragment = new GroupEditDialogFragment();
|
||||
groupEditDialogFragment.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
GroupEditDialogFragment.build()
|
||||
.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
});
|
||||
addNodeButtonView.setAddEntryClickListener(v ->
|
||||
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
|
||||
|
||||
setGroupTitle();
|
||||
setGroupIcon();
|
||||
|
||||
Log.w(TAG, "Finished creating tree");
|
||||
|
||||
@@ -206,7 +220,6 @@ public class GroupActivity extends ListNodesActivity
|
||||
nodeAdapter.setNodeMenuListener(new NodeAdapter.NodeMenuListener() {
|
||||
@Override
|
||||
public boolean onOpenMenuClick(PwNode node) {
|
||||
mAdapter.registerANodeToUpdate(node);
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
GroupActivity.launch(GroupActivity.this, (PwGroup) node);
|
||||
@@ -220,14 +233,12 @@ public class GroupActivity extends ListNodesActivity
|
||||
|
||||
@Override
|
||||
public boolean onEditMenuClick(PwNode node) {
|
||||
mAdapter.registerANodeToUpdate(node);
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
editGroupDialogAction = EditGroupDialogAction.UPDATE;
|
||||
GroupEditDialogFragment groupEditDialogFragment =
|
||||
GroupEditDialogFragment.build(node);
|
||||
groupEditDialogFragment.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
oldGroupToUpdate = (PwGroup) node;
|
||||
GroupEditDialogFragment.build(node)
|
||||
.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntryEditActivity.launch(GroupActivity.this, (PwEntry) node);
|
||||
@@ -254,10 +265,139 @@ public class GroupActivity extends ListNodesActivity
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Refresh the group icon
|
||||
assignGroupIcon();
|
||||
// Show button on resume
|
||||
addNodeButtonView.showButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and display learning views
|
||||
* Displays the explanation for a add, search, sort a new node and lock the database
|
||||
*/
|
||||
private void checkAndPerformedEducation(Menu menu) {
|
||||
|
||||
// If no node, show education to add new one
|
||||
if (mAdapter.getItemCount() <= 0) {
|
||||
if (!PreferencesUtil.isEducationNewNodePerformed(this)) {
|
||||
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forView(findViewById(R.id.add_button),
|
||||
getString(R.string.education_new_node_title),
|
||||
getString(R.string.education_new_node_summary))
|
||||
.tintTarget(false)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
addNodeButtonView.openButtonIfClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(this,
|
||||
R.string.education_new_node_key);
|
||||
|
||||
}
|
||||
}
|
||||
// Else show the search education
|
||||
else if (!PreferencesUtil.isEducationSearchPerformed(this)) {
|
||||
|
||||
try {
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forToolbarMenuItem(toolbar, R.id.menu_search,
|
||||
getString(R.string.education_search_title),
|
||||
getString(R.string.education_search_summary))
|
||||
.tintTarget(true)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
MenuItem searchItem = menu.findItem(R.id.menu_search);
|
||||
searchItem.expandActionView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(this,
|
||||
R.string.education_search_key);
|
||||
} catch (Exception e) {
|
||||
// If icon not visible
|
||||
Log.w(TAG, "Can't performed education for search");
|
||||
}
|
||||
}
|
||||
// Else show the sort education
|
||||
else if (!PreferencesUtil.isEducationSortPerformed(this)) {
|
||||
|
||||
try {
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forToolbarMenuItem(toolbar, R.id.menu_sort,
|
||||
getString(R.string.education_sort_title),
|
||||
getString(R.string.education_sort_summary))
|
||||
.tintTarget(true)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
MenuItem sortItem = menu.findItem(R.id.menu_sort);
|
||||
onOptionsItemSelected(sortItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(this,
|
||||
R.string.education_sort_key);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Can't performed education for sort");
|
||||
}
|
||||
}
|
||||
// Else show the lock education
|
||||
else if (!PreferencesUtil.isEducationLockPerformed(this)) {
|
||||
|
||||
try {
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forToolbarMenuItem(toolbar, R.id.menu_lock,
|
||||
getString(R.string.education_lock_title),
|
||||
getString(R.string.education_lock_summary))
|
||||
.tintTarget(true)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
MenuItem lockItem = menu.findItem(R.id.menu_lock);
|
||||
onOptionsItemSelected(lockItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(this,
|
||||
R.string.education_lock_key);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Can't performed education for lock");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
@@ -272,10 +412,20 @@ public class GroupActivity extends ListNodesActivity
|
||||
addNodeButtonView.showButton();
|
||||
}
|
||||
|
||||
protected void setGroupIcon() {
|
||||
/**
|
||||
* Assign the group icon depending of IconPack or custom icon
|
||||
*/
|
||||
protected void assignGroupIcon() {
|
||||
if (mCurrentGroup != null) {
|
||||
ImageView iv = findViewById(R.id.icon);
|
||||
App.getDB().getDrawFactory().assignDrawableTo(iv, getResources(), mCurrentGroup.getIcon());
|
||||
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
|
||||
// Retrieve the textColor to tint the icon
|
||||
int[] attrs = {R.attr.textColorInverse};
|
||||
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
||||
int iconColor = ta.getColor(0, Color.WHITE);
|
||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), true, iconColor);
|
||||
} else {
|
||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +448,6 @@ public class GroupActivity extends ListNodesActivity
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.search, menu);
|
||||
@@ -320,6 +469,11 @@ public class GroupActivity extends ListNodesActivity
|
||||
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
|
||||
}
|
||||
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
// Launch education screen
|
||||
new Handler().post(() -> checkAndPerformedEducation(menu));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -376,28 +530,68 @@ public class GroupActivity extends ListNodesActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approveEditGroup(Bundle bundle) {
|
||||
String GroupName = bundle.getString(GroupEditDialogFragment.KEY_NAME);
|
||||
int GroupIconID = bundle.getInt(GroupEditDialogFragment.KEY_ICON_ID);
|
||||
switch (editGroupDialogAction) {
|
||||
public void approveEditGroup(GroupEditDialogFragment.EditGroupDialogAction action,
|
||||
String name,
|
||||
PwIcon icon) {
|
||||
Database database = App.getDB();
|
||||
PwIconStandard iconStandard = database.getPwDatabase().getIconFactory().getFirstIcon();
|
||||
|
||||
switch (action) {
|
||||
case CREATION:
|
||||
// If edit group creation
|
||||
Handler handler = new Handler();
|
||||
AddGroup task = new AddGroup(this, App.getDB(), GroupName, GroupIconID, mCurrentGroup,
|
||||
new AfterAddNode(handler), false);
|
||||
ProgressTask pt = new ProgressTask(this, task, R.string.saving_database);
|
||||
pt.run();
|
||||
// If group creation
|
||||
// Build the group
|
||||
PwGroup newGroup = database.createGroup(mCurrentGroup);
|
||||
newGroup.setName(name);
|
||||
try {
|
||||
iconStandard = (PwIconStandard) icon;
|
||||
} catch (Exception e) {} // TODO custom icon
|
||||
newGroup.setIcon(iconStandard);
|
||||
|
||||
new ProgressTask(this,
|
||||
new AddGroup(this,
|
||||
App.getDB(),
|
||||
newGroup,
|
||||
new AfterAddNode(new Handler())),
|
||||
R.string.saving_database)
|
||||
.run();
|
||||
break;
|
||||
case UPDATE:
|
||||
// If edit group update
|
||||
// TODO UpdateGroup
|
||||
// If update add new elements
|
||||
if (oldGroupToUpdate != null) {
|
||||
PwGroup updateGroup = oldGroupToUpdate.clone();
|
||||
updateGroup.setName(name);
|
||||
try {
|
||||
iconStandard = (PwIconStandard) icon;
|
||||
} catch (Exception e) {} // TODO custom icon
|
||||
updateGroup.setIcon(iconStandard);
|
||||
|
||||
mAdapter.removeNode(oldGroupToUpdate);
|
||||
// If group update
|
||||
new ProgressTask(this,
|
||||
new UpdateGroup(this,
|
||||
App.getDB(),
|
||||
oldGroupToUpdate,
|
||||
updateGroup,
|
||||
new AfterUpdateNode(new Handler())),
|
||||
R.string.saving_database)
|
||||
.run();
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
editGroupDialogAction = EditGroupDialogAction.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEditGroup(Bundle bundle) {
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
outState.putSerializable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEditGroup(GroupEditDialogFragment.EditGroupDialogAction action,
|
||||
String name,
|
||||
PwIcon iconId) {
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
import com.kunzisoft.keepass.database.PwNode;
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||
import com.kunzisoft.keepass.database.edit.AfterAddNodeOnFinish;
|
||||
import com.kunzisoft.keepass.database.edit.AfterActionNodeOnFinish;
|
||||
import com.kunzisoft.keepass.database.edit.OnFinish;
|
||||
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
|
||||
@@ -143,8 +143,6 @@ public abstract class ListNodesActivity extends LockingActivity
|
||||
@Override
|
||||
public void onNodeClick(PwNode node) {
|
||||
|
||||
mAdapter.registerANodeToUpdate(node);
|
||||
|
||||
// Add event when we have Autofill
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -244,7 +242,7 @@ public abstract class ListNodesActivity extends LockingActivity
|
||||
|
||||
AssignPasswordHelper assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPassword, keyFile);
|
||||
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
|
||||
assignPasswordHelper.assignPasswordInDatabase(null);
|
||||
}
|
||||
|
||||
@@ -302,15 +300,30 @@ public abstract class ListNodesActivity extends LockingActivity
|
||||
}
|
||||
}
|
||||
|
||||
class AfterAddNode extends AfterAddNodeOnFinish {
|
||||
class AfterAddNode extends AfterActionNodeOnFinish {
|
||||
AfterAddNode(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
public void run(PwNode pwNode) {
|
||||
public void run(PwNode oldNode, PwNode newNode) {
|
||||
super.run();
|
||||
if (mSuccess) {
|
||||
mAdapter.addNode(pwNode);
|
||||
mAdapter.addNode(newNode);
|
||||
} else {
|
||||
displayMessage(ListNodesActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AfterUpdateNode extends AfterActionNodeOnFinish {
|
||||
AfterUpdateNode(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
public void run(PwNode oldNode, PwNode newNode) {
|
||||
super.run();
|
||||
if (mSuccess) {
|
||||
mAdapter.updateNode(oldNode, newNode);
|
||||
} else {
|
||||
displayMessage(ListNodesActivity.this);
|
||||
}
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
package com.kunzisoft.keepass.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.util.SortedListAdapterCallback;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -36,6 +38,7 @@ import com.kunzisoft.keepass.app.App;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
import com.kunzisoft.keepass.database.PwNode;
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||
|
||||
public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
@@ -50,10 +53,12 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
private boolean ascendingSort;
|
||||
|
||||
private OnNodeClickCallback onNodeClickCallback;
|
||||
private int nodePositionToUpdate;
|
||||
private NodeMenuListener nodeMenuListener;
|
||||
private boolean activateContextMenu;
|
||||
|
||||
private int iconGroupColor;
|
||||
private int iconEntryColor;
|
||||
|
||||
/**
|
||||
* Create node list adapter with contextMenu or not
|
||||
* @param context Context to use
|
||||
@@ -66,7 +71,6 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
|
||||
this.ascendingSort = PreferencesUtil.getAscendingSort(context);
|
||||
this.activateContextMenu = false;
|
||||
this.nodePositionToUpdate = -1;
|
||||
|
||||
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
|
||||
@Override public int compare(PwNode item1, PwNode item2) {
|
||||
@@ -81,6 +85,16 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
return item1.equals(item2);
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieve the color to tint the icon
|
||||
int[] attrTextColorPrimary = {android.R.attr.textColorPrimary};
|
||||
TypedArray taTextColorPrimary = context.getTheme().obtainStyledAttributes(attrTextColorPrimary);
|
||||
this.iconGroupColor = taTextColorPrimary.getColor(0, Color.BLACK);
|
||||
taTextColorPrimary.recycle();
|
||||
int[] attrTextColor = {android.R.attr.textColor}; // In two times to fix bug compilation
|
||||
TypedArray taTextColor = context.getTheme().obtainStyledAttributes(attrTextColor);
|
||||
this.iconEntryColor = taTextColor.getColor(0, Color.BLACK);
|
||||
taTextColor.recycle();
|
||||
}
|
||||
|
||||
public void setActivateContextMenu(boolean activate) {
|
||||
@@ -106,43 +120,25 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a node to update before an action
|
||||
* Call updateLastNodeRegister() after the action to update the node
|
||||
* @param node Node to register
|
||||
*/
|
||||
public void registerANodeToUpdate(PwNode node) {
|
||||
nodePositionToUpdate = nodeSortedList.indexOf(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the last Node register in the list
|
||||
* Work if only registerANodeToUpdate(PwNode node) is called before
|
||||
*/
|
||||
public void updateLastNodeRegister(PwNode node) {
|
||||
// Don't really update here, sorted list knows each original ref, so we just notify a change
|
||||
try {
|
||||
if (nodePositionToUpdate != -1) {
|
||||
// Don't know why but there is a bug to remove a node after this update
|
||||
nodeSortedList.updateItemAt(nodePositionToUpdate, node);
|
||||
nodeSortedList.recalculatePositionOfItemAt(nodePositionToUpdate);
|
||||
nodePositionToUpdate = -1;
|
||||
}
|
||||
else {
|
||||
Log.e(NodeAdapter.class.getName(), "registerANodeToUpdate must be called before updateLastNodeRegister");
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
Log.e(NodeAdapter.class.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove node in the list
|
||||
* Remove a node in the list
|
||||
* @param node Node to delete
|
||||
*/
|
||||
public void removeNode(PwNode node) {
|
||||
nodeSortedList.remove(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a node in the list
|
||||
* @param oldNode Node before the update
|
||||
* @param newNode Node after the update
|
||||
*/
|
||||
public void updateNode(PwNode oldNode, PwNode newNode) {
|
||||
nodeSortedList.beginBatchedUpdates();
|
||||
nodeSortedList.remove(oldNode);
|
||||
nodeSortedList.add(newNode);
|
||||
nodeSortedList.endBatchedUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify a change sort of the list
|
||||
*/
|
||||
@@ -157,8 +153,9 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
return nodeSortedList.get(position).getType().ordinal();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BasicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
public BasicViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
BasicViewHolder basicViewHolder;
|
||||
View view;
|
||||
if (viewType == PwNode.Type.GROUP.ordinal()) {
|
||||
@@ -172,11 +169,23 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(BasicViewHolder holder, int position) {
|
||||
public void onBindViewHolder(@NonNull BasicViewHolder holder, int position) {
|
||||
PwNode subNode = nodeSortedList.get(position);
|
||||
// Assign image
|
||||
App.getDB().getDrawFactory().assignDrawableTo(holder.icon,
|
||||
context.getResources(), subNode.getIcon());
|
||||
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
|
||||
int iconColor = Color.BLACK;
|
||||
switch (subNode.getType()) {
|
||||
case GROUP:
|
||||
iconColor = iconGroupColor;
|
||||
break;
|
||||
case ENTRY:
|
||||
iconColor = iconEntryColor;
|
||||
break;
|
||||
}
|
||||
App.getDB().getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon(), true, iconColor);
|
||||
} else {
|
||||
App.getDB().getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon());
|
||||
}
|
||||
// Assign text
|
||||
holder.text.setText(subNode.getDisplayTitle());
|
||||
// Assign click
|
||||
@@ -265,9 +274,10 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
MenuItem clearMenu = contextMenu.add(Menu.NONE, MENU_OPEN, Menu.NONE, R.string.menu_open);
|
||||
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
if (!App.getDB().isReadOnly() && !node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
|
||||
// TODO make edit for group
|
||||
// clearMenu = contextMenu.add(Menu.NONE, MENU_EDIT, Menu.NONE, R.string.menu_edit);
|
||||
// clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
// Edition
|
||||
clearMenu = contextMenu.add(Menu.NONE, MENU_EDIT, Menu.NONE, R.string.menu_edit);
|
||||
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
// Deletion
|
||||
clearMenu = contextMenu.add(Menu.NONE, MENU_DELETE, Menu.NONE, R.string.menu_delete);
|
||||
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class App extends MultiDexApplication {
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
public static RecentFileHistory getFileHistory() {
|
||||
return fileHistory;
|
||||
}
|
||||
|
||||
@@ -28,18 +28,19 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
import com.kunzisoft.keepass.fileselect.FileSelectActivity;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class AutoFillAuthActivity extends KeePass {
|
||||
public class AutoFillAuthActivity extends AppCompatActivity {
|
||||
|
||||
private AutofillHelper autofillHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
startFileSelectActivity();
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@@ -48,8 +49,7 @@ public class AutoFillAuthActivity extends KeePass {
|
||||
return PendingIntent.getActivity(context, 0,
|
||||
intent, PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
protected void startFileSelectActivity() {
|
||||
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
||||
AssistStructure assistStructure = autofillHelper.retrieveAssistStructure(getIntent());
|
||||
|
||||
@@ -34,7 +34,7 @@ import com.kunzisoft.keepass.database.exception.PwDbOutputException;
|
||||
import com.kunzisoft.keepass.database.load.Importer;
|
||||
import com.kunzisoft.keepass.database.load.ImporterFactory;
|
||||
import com.kunzisoft.keepass.database.save.PwDbOutput;
|
||||
import com.kunzisoft.keepass.icons.DrawableFactory;
|
||||
import com.kunzisoft.keepass.icons.IconDrawableFactory;
|
||||
import com.kunzisoft.keepass.search.SearchDbHelper;
|
||||
import com.kunzisoft.keepass.tasks.UpdateStatus;
|
||||
import com.kunzisoft.keepass.utils.UriUtil;
|
||||
@@ -59,7 +59,7 @@ public class Database {
|
||||
private boolean readOnly = false;
|
||||
private boolean passwordEncodingError = false;
|
||||
|
||||
private DrawableFactory drawFactory = new DrawableFactory();
|
||||
private IconDrawableFactory drawFactory = new IconDrawableFactory();
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
@@ -87,7 +87,7 @@ public class Database {
|
||||
return passwordEncodingError;
|
||||
}
|
||||
|
||||
public DrawableFactory getDrawFactory() {
|
||||
public IconDrawableFactory getDrawFactory() {
|
||||
return drawFactory;
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ public class Database {
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
drawFactory.clear();
|
||||
drawFactory.clearCache();
|
||||
|
||||
pm = null;
|
||||
mUri = null;
|
||||
@@ -368,6 +368,37 @@ public class Database {
|
||||
getPwDatabase().setNumberKeyEncryptionRounds(numberRounds);
|
||||
}
|
||||
|
||||
public PwEntry createEntry(PwGroup parent) {
|
||||
PwEntry newPwEntry = null;
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
case V3:
|
||||
newPwEntry = new PwEntryV3((PwGroupV3) parent);
|
||||
case V4:
|
||||
newPwEntry = new PwEntryV4((PwGroupV4) parent);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "This version of PwEntry can't be created", e);
|
||||
}
|
||||
return newPwEntry;
|
||||
}
|
||||
|
||||
public PwGroup createGroup(PwGroup parent) {
|
||||
PwGroup newPwGroup = null;
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
case V3:
|
||||
newPwGroup = new PwGroupV3((PwGroupV3) parent);
|
||||
case V4:
|
||||
newPwGroup = new PwGroupV4((PwGroupV4) parent);
|
||||
}
|
||||
newPwGroup.setId(pm.newGroupId());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "This version of PwGroup can't be created", e);
|
||||
}
|
||||
return newPwGroup;
|
||||
}
|
||||
|
||||
public void addEntryTo(PwEntry entry, PwGroup parent) {
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
@@ -501,6 +532,21 @@ public class Database {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateGroup(PwGroup oldGroup, PwGroup newGroup) {
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
case V3:
|
||||
((PwGroupV3) oldGroup).updateWith((PwGroupV3) newGroup);
|
||||
break;
|
||||
case V4:
|
||||
((PwGroupV4) oldGroup).updateWith((PwGroupV4) newGroup);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "This version of PwEntry can't be updated", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteEntry(PwEntry entry) {
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
|
||||
@@ -134,8 +134,6 @@ public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB,
|
||||
|
||||
public void setMasterKey(String key, InputStream keyInputStream)
|
||||
throws InvalidKeyFileException, IOException {
|
||||
assert(key != null);
|
||||
|
||||
masterKey = getMasterKey(key, keyInputStream);
|
||||
}
|
||||
|
||||
@@ -220,6 +218,9 @@ public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB,
|
||||
}
|
||||
|
||||
public boolean validatePasswordEncoding(String key) {
|
||||
if (key == null)
|
||||
return false;
|
||||
|
||||
String encoding = getPasswordEncoding();
|
||||
|
||||
byte[] bKey;
|
||||
@@ -243,10 +244,8 @@ public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB,
|
||||
protected abstract String getPasswordEncoding();
|
||||
|
||||
public byte[] getPasswordKey(String key) throws IOException {
|
||||
assert(key!=null);
|
||||
|
||||
if ( key.length() == 0 )
|
||||
throw new IllegalArgumentException( "Key cannot be empty." );
|
||||
if ( key == null)
|
||||
throw new IllegalArgumentException( "Key cannot be empty." ); // TODO
|
||||
|
||||
MessageDigest md;
|
||||
try {
|
||||
|
||||
@@ -45,7 +45,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
package com.kunzisoft.keepass.database;
|
||||
|
||||
// Java
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.AesKdf;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException;
|
||||
|
||||
@@ -71,13 +70,6 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
||||
|
||||
private int numKeyEncRounds;
|
||||
|
||||
private void initAndAddGroup(String name, int iconId, PwGroupV3 parent) {
|
||||
PwGroupV3 group = createGroup();
|
||||
group.initNewGroup(name, newGroupId());
|
||||
group.setIcon(iconFactory.getIcon(iconId));
|
||||
addGroupTo(group, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initNew(String dbPath) {
|
||||
algorithm = PwEncryptionAlgorithm.AES_Rijndael;
|
||||
@@ -90,6 +82,14 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
||||
initAndAddGroup("eMail", 19, rootGroup);
|
||||
}
|
||||
|
||||
private void initAndAddGroup(String name, int iconId, PwGroupV3 parent) {
|
||||
PwGroupV3 group = createGroup();
|
||||
group.setId(newGroupId());
|
||||
group.setName(name);
|
||||
group.setIcon(iconFactory.getIcon(iconId));
|
||||
addGroupTo(group, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwVersion getVersion() {
|
||||
return PwVersion.V3;
|
||||
@@ -235,14 +235,15 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
||||
return newId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getMasterKey(String key, InputStream keyInputStream)
|
||||
throws InvalidKeyFileException, IOException {
|
||||
|
||||
if (key != null && key.length() > 0 && keyInputStream != null) {
|
||||
if (key != null && keyInputStream != null) {
|
||||
return getCompositeKey(key, keyInputStream);
|
||||
} else if (key != null && key.length() > 0) {
|
||||
} else if (key != null) { // key.length() >= 0
|
||||
return getPasswordKey(key);
|
||||
} else if (keyInputStream != null) {
|
||||
} else if (keyInputStream != null) { // key == null
|
||||
return getFileKey(keyInputStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Key cannot be empty.");
|
||||
|
||||
@@ -338,15 +338,14 @@ public class PwDatabaseV4 extends PwDatabase<PwGroupV4, PwEntryV4> {
|
||||
@Override
|
||||
public byte[] getMasterKey(String key, InputStream keyInputStream)
|
||||
throws InvalidKeyFileException, IOException {
|
||||
assert(key != null);
|
||||
|
||||
byte[] fKey = new byte[]{};
|
||||
|
||||
if ( key.length() > 0 && keyInputStream != null) {
|
||||
if (key != null && keyInputStream != null) {
|
||||
return getCompositeKey(key, keyInputStream);
|
||||
} else if ( key.length() > 0 ) {
|
||||
} else if (key != null) { // key.length() >= 0
|
||||
fKey = getPasswordKey(key);
|
||||
} else if ( keyInputStream != null) {
|
||||
} else if (keyInputStream != null) { // key == null
|
||||
fKey = getFileKey(keyInputStream);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> implements Cloneable {
|
||||
public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
|
||||
|
||||
private static final String PMS_TAN_ENTRY = "<TAN>";
|
||||
|
||||
@@ -36,35 +36,12 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> imp
|
||||
uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
public static PwEntry getInstance(PwGroup parent) {
|
||||
if (parent instanceof PwGroupV3) {
|
||||
return new PwEntryV3((PwGroupV3)parent);
|
||||
}
|
||||
else if (parent instanceof PwGroupV4) {
|
||||
return new PwEntryV4((PwGroupV4)parent);
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Unknown PwGroup instance.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwEntry clone() {
|
||||
PwEntry newEntry;
|
||||
try {
|
||||
newEntry = (PwEntry) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException("Clone should be supported");
|
||||
}
|
||||
return newEntry;
|
||||
// uuid is clone automatically (IMMUTABLE)
|
||||
return (PwEntry) super.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCloneAttributesToNewEntry(PwEntry newEntry) {
|
||||
super.addCloneAttributesToNewEntry(newEntry);
|
||||
// uuid is clone automatically (IMMUTABLE)
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.ENTRY;
|
||||
|
||||
@@ -98,6 +98,7 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
|
||||
}
|
||||
|
||||
protected void updateWith(PwEntryV3 source) {
|
||||
super.assign(source);
|
||||
groupId = source.groupId;
|
||||
|
||||
title = source.title;
|
||||
@@ -118,10 +119,8 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
|
||||
|
||||
@Override
|
||||
public PwEntryV3 clone() {
|
||||
PwEntryV3 newEntry = (PwEntryV3) super.clone();
|
||||
|
||||
// Attributes in parent
|
||||
addCloneAttributesToNewEntry(newEntry);
|
||||
PwEntryV3 newEntry = (PwEntryV3) super.clone();
|
||||
|
||||
// Attributes here
|
||||
// newEntry.parent stay the same in copy
|
||||
|
||||
@@ -89,11 +89,9 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PwEntryV4 clone() {
|
||||
// Attributes in parent
|
||||
PwEntryV4 newEntry = (PwEntryV4) super.clone();
|
||||
|
||||
// Attributes in parent
|
||||
addCloneAttributesToNewEntry(newEntry);
|
||||
|
||||
// Attributes here
|
||||
newEntry.customIcon = new PwIconCustom(this.customIcon);
|
||||
// newEntry.usageCount stay the same in copy
|
||||
|
||||
@@ -30,9 +30,15 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
|
||||
protected List<ChildGroup> childGroups = new ArrayList<>();
|
||||
protected List<ChildEntry> childEntries = new ArrayList<>();
|
||||
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
setId(newId);
|
||||
name = nm;
|
||||
@Override
|
||||
public PwGroup clone() {
|
||||
// name is clone automatically (IMMUTABLE)
|
||||
return (PwGroup) super.clone();
|
||||
}
|
||||
|
||||
protected void assign(PwGroup<Parent, ChildGroup, ChildEntry> source) {
|
||||
super.assign(source);
|
||||
name = source.name;
|
||||
}
|
||||
|
||||
public List<ChildGroup> getChildGroups() {
|
||||
|
||||
@@ -40,6 +40,26 @@ public class PwGroupV3 extends PwGroup<PwGroupV3, PwGroupV3, PwEntryV3> {
|
||||
super();
|
||||
}
|
||||
|
||||
public PwGroupV3(PwGroupV3 p) {
|
||||
construct(p);
|
||||
}
|
||||
|
||||
protected void updateWith(PwGroupV3 source) {
|
||||
super.assign(source);
|
||||
groupId = source.groupId;
|
||||
level = source.level;
|
||||
flags = source.flags;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PwGroupV3 clone() {
|
||||
// newGroup.groupId stay the same in copy
|
||||
// newGroup.level stay the same in copy
|
||||
// newGroup.flags stay the same in copy
|
||||
return (PwGroupV3) super.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(PwGroupV3 parent) {
|
||||
super.setParent(parent);
|
||||
|
||||
@@ -45,6 +45,11 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
|
||||
public PwGroupV4() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PwGroupV4(PwGroupV4 p) {
|
||||
construct(p);
|
||||
parentGroupLastMod = new PwDate();
|
||||
}
|
||||
|
||||
public PwGroupV4(String name, PwIconStandard icon) {
|
||||
this.uuid = UUID.randomUUID();
|
||||
@@ -52,10 +57,47 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
protected void updateWith(PwGroupV4 source) {
|
||||
super.assign(source);
|
||||
uuid = source.uuid;
|
||||
customIcon = source.customIcon;
|
||||
usageCount = source.usageCount;
|
||||
parentGroupLastMod = source.parentGroupLastMod;
|
||||
customData = source.customData;
|
||||
|
||||
expires = source.expires;
|
||||
|
||||
notes = source.notes;
|
||||
isExpanded = source.isExpanded;
|
||||
defaultAutoTypeSequence = source.defaultAutoTypeSequence;
|
||||
enableAutoType = source.enableAutoType;
|
||||
enableSearching = source.enableSearching;
|
||||
lastTopVisibleEntry = source.lastTopVisibleEntry;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
super.initNewGroup(nm, newId);
|
||||
parentGroupLastMod = new PwDate();
|
||||
public PwGroupV4 clone() {
|
||||
// Attributes in parent
|
||||
PwGroupV4 newGroup = (PwGroupV4) super.clone();
|
||||
|
||||
// Attributes here
|
||||
// newGroup.uuid stay the same in copy
|
||||
newGroup.customIcon = new PwIconCustom(this.customIcon);
|
||||
// newGroup.usageCount stay the same in copy
|
||||
newGroup.parentGroupLastMod = this.parentGroupLastMod.clone();
|
||||
// TODO customData make copy from hashmap newGroup.customData = (HashMap<String, String>) this.customData.clone();
|
||||
|
||||
// newGroup.expires stay the same in copy
|
||||
|
||||
// newGroup.notes stay the same in copy
|
||||
// newGroup.isExpanded stay the same in copy
|
||||
// newGroup.defaultAutoTypeSequence stay the same in copy
|
||||
// newGroup.enableAutoType stay the same in copy
|
||||
// newGroup.enableSearching stay the same in copy
|
||||
// newGroup.lastTopVisibleEntry stay the same in copy
|
||||
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
public void addGroup(PwGroupV4 subGroup) {
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.io.Serializable;
|
||||
/**
|
||||
* Abstract class who manage Groups and Entries
|
||||
*/
|
||||
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Serializable {
|
||||
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Serializable, Cloneable {
|
||||
|
||||
protected Parent parent = null;
|
||||
|
||||
@@ -53,15 +53,23 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
|
||||
this.expireDate = source.expireDate;
|
||||
}
|
||||
|
||||
protected void addCloneAttributesToNewEntry(PwEntry newEntry) {
|
||||
// newEntry.parent stay the same in copy
|
||||
@Override
|
||||
public PwNode clone() {
|
||||
PwNode newNode;
|
||||
try {
|
||||
newNode = (PwNode) super.clone();
|
||||
// newNode.parent stay the same in copy
|
||||
|
||||
newEntry.icon = new PwIconStandard(this.icon);
|
||||
newNode.icon = new PwIconStandard(this.icon);
|
||||
|
||||
newEntry.creation = creation.clone();
|
||||
newEntry.lastMod = lastMod.clone();
|
||||
newEntry.lastAccess = lastAccess.clone();
|
||||
newEntry.expireDate = expireDate.clone();
|
||||
newNode.creation = creation.clone();
|
||||
newNode.lastMod = lastMod.clone();
|
||||
newNode.lastAccess = lastAccess.clone();
|
||||
newNode.expireDate = expireDate.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException("Clone should be supported");
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,13 +29,19 @@ public class AddEntry extends RunnableOnFinish {
|
||||
protected Database mDb;
|
||||
private PwEntry mEntry;
|
||||
private Context ctx;
|
||||
|
||||
private boolean mDontSave;
|
||||
|
||||
public AddEntry(Context ctx, Database db, PwEntry entry, OnFinish finish) {
|
||||
this(ctx, db, entry, finish, false);
|
||||
}
|
||||
|
||||
public AddEntry(Context ctx, Database db, PwEntry entry, OnFinish finish, boolean dontSave) {
|
||||
super(finish);
|
||||
|
||||
this.mDb = db;
|
||||
this.mEntry = entry;
|
||||
this.ctx = ctx;
|
||||
this.mDontSave = dontSave;
|
||||
|
||||
this.mFinish = new AfterAdd(mFinish);
|
||||
}
|
||||
@@ -45,7 +51,7 @@ public class AddEntry extends RunnableOnFinish {
|
||||
mDb.addEntryTo(mEntry, mEntry.getParent());
|
||||
|
||||
// Commit to disk
|
||||
SaveDB save = new SaveDB(ctx, mDb, mFinish);
|
||||
SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave);
|
||||
save.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,28 +22,25 @@ package com.kunzisoft.keepass.database.edit;
|
||||
import android.content.Context;
|
||||
|
||||
import com.kunzisoft.keepass.database.Database;
|
||||
import com.kunzisoft.keepass.database.PwDatabase;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
|
||||
public class AddGroup extends RunnableOnFinish {
|
||||
|
||||
protected Database mDb;
|
||||
private String mName;
|
||||
private int mIconID;
|
||||
private PwGroup mGroup;
|
||||
private PwGroup mParent;
|
||||
private PwGroup mNewGroup;
|
||||
private Context ctx;
|
||||
private boolean mDontSave;
|
||||
|
||||
public AddGroup(Context ctx, Database db, String name, int iconid,
|
||||
PwGroup parent, AfterAddNodeOnFinish afterAddNode,
|
||||
|
||||
public AddGroup(Context ctx, Database db, PwGroup newGroup, AfterActionNodeOnFinish afterAddNode) {
|
||||
this(ctx, db, newGroup, afterAddNode, false);
|
||||
}
|
||||
|
||||
public AddGroup(Context ctx, Database db, PwGroup newGroup, AfterActionNodeOnFinish afterAddNode,
|
||||
boolean dontSave) {
|
||||
super(afterAddNode);
|
||||
|
||||
this.mDb = db;
|
||||
this.mName = name;
|
||||
this.mIconID = iconid;
|
||||
this.mParent = parent;
|
||||
this.mNewGroup = newGroup;
|
||||
this.mDontSave = dontSave;
|
||||
this.ctx = ctx;
|
||||
|
||||
@@ -52,13 +49,7 @@ public class AddGroup extends RunnableOnFinish {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PwDatabase pm = mDb.getPwDatabase();
|
||||
|
||||
// Generate new group
|
||||
mGroup = pm.createGroup();
|
||||
mGroup.initNewGroup(mName, pm.newGroupId());
|
||||
mGroup.setIcon(pm.getIconFactory().getIcon(mIconID));
|
||||
mDb.addGroupTo(mGroup, mParent);
|
||||
mDb.addGroupTo(mNewGroup, mNewGroup.getParent());
|
||||
|
||||
// Commit to disk
|
||||
SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave);
|
||||
@@ -74,15 +65,15 @@ public class AddGroup extends RunnableOnFinish {
|
||||
@Override
|
||||
public void run() {
|
||||
if ( !mSuccess ) {
|
||||
mDb.removeGroupFrom(mGroup, mParent);
|
||||
mDb.removeGroupFrom(mNewGroup, mNewGroup.getParent());
|
||||
}
|
||||
|
||||
// TODO Better callback
|
||||
AfterAddNodeOnFinish afterAddNode =
|
||||
(AfterAddNodeOnFinish) super.mOnFinish;
|
||||
AfterActionNodeOnFinish afterAddNode =
|
||||
(AfterActionNodeOnFinish) super.mOnFinish;
|
||||
afterAddNode.mSuccess = mSuccess;
|
||||
afterAddNode.mMessage = mMessage;
|
||||
afterAddNode.run(mGroup);
|
||||
afterAddNode.run(null, mNewGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ import android.os.Handler;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwNode;
|
||||
|
||||
public abstract class AfterAddNodeOnFinish extends OnFinish {
|
||||
public AfterAddNodeOnFinish(Handler handler) {
|
||||
public abstract class AfterActionNodeOnFinish extends OnFinish {
|
||||
public AfterActionNodeOnFinish(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
public abstract void run(PwNode pwNode);
|
||||
public abstract void run(PwNode oldNode, PwNode newNode);
|
||||
}
|
||||
@@ -45,11 +45,6 @@ public class DeleteGroup extends RunnableOnFinish {
|
||||
super(finish);
|
||||
setMembers(ctx, db, group, dontSave);
|
||||
}
|
||||
|
||||
public DeleteGroup(Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, OnFinish finish, boolean dontSave) {
|
||||
super(finish);
|
||||
setMembers(null, db, group, dontSave);
|
||||
}
|
||||
|
||||
private void setMembers(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, boolean dontSave) {
|
||||
mDb = db;
|
||||
|
||||
@@ -58,11 +58,8 @@ public class SaveDB extends RunnableOnFinish {
|
||||
return;
|
||||
} catch (PwDbOutputException e) {
|
||||
// TODO: Restore
|
||||
throw new RuntimeException(e);
|
||||
/*
|
||||
finish(false, e.getMessage());
|
||||
return;
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,14 +30,20 @@ public class UpdateEntry extends RunnableOnFinish {
|
||||
private PwEntry mOldE;
|
||||
private PwEntry mNewE;
|
||||
private Context ctx;
|
||||
|
||||
private boolean mDontSave;
|
||||
|
||||
public UpdateEntry(Context ctx, Database db, PwEntry oldE, PwEntry newE, OnFinish finish) {
|
||||
this(ctx, db, oldE, newE, finish, false);
|
||||
}
|
||||
|
||||
public UpdateEntry(Context ctx, Database db, PwEntry oldE, PwEntry newE, OnFinish finish, boolean dontSave) {
|
||||
super(finish);
|
||||
|
||||
mDb = db;
|
||||
mOldE = oldE;
|
||||
mNewE = newE;
|
||||
this.mDb = db;
|
||||
this.mOldE = oldE;
|
||||
this.mNewE = newE;
|
||||
this.ctx = ctx;
|
||||
this.mDontSave = dontSave;
|
||||
|
||||
// Keep backup of original values in case save fails
|
||||
PwEntry backup;
|
||||
@@ -53,7 +59,7 @@ public class UpdateEntry extends RunnableOnFinish {
|
||||
mOldE.touch(true, true);
|
||||
|
||||
// Commit to disk
|
||||
SaveDB save = new SaveDB(ctx, mDb, mFinish);
|
||||
SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave);
|
||||
save.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.edit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.kunzisoft.keepass.database.Database;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
|
||||
public class UpdateGroup extends RunnableOnFinish {
|
||||
|
||||
private Database mDb;
|
||||
private PwGroup mOldGroup;
|
||||
private PwGroup mNewGroup;
|
||||
private Context ctx;
|
||||
private boolean mDontSave;
|
||||
|
||||
public UpdateGroup(Context ctx, Database db, PwGroup oldGroup, PwGroup newGroup, AfterActionNodeOnFinish finish) {
|
||||
this(ctx, db, oldGroup, newGroup, finish, false);
|
||||
}
|
||||
|
||||
public UpdateGroup(Context ctx, Database db, PwGroup oldGroup, PwGroup newGroup, AfterActionNodeOnFinish finish, boolean dontSave) {
|
||||
super(finish);
|
||||
|
||||
this.mDb = db;
|
||||
this.mOldGroup = oldGroup;
|
||||
this.mNewGroup = newGroup;
|
||||
this.ctx = ctx;
|
||||
this.mDontSave = dontSave;
|
||||
|
||||
// Keep backup of original values in case save fails
|
||||
PwGroup backup;
|
||||
backup = mOldGroup.clone();
|
||||
|
||||
this.mFinish = new AfterUpdate(backup, finish);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Update group with new values
|
||||
mDb.updateGroup(mOldGroup, mNewGroup);
|
||||
mOldGroup.touch(true, true);
|
||||
|
||||
// Commit to disk
|
||||
new SaveDB(ctx, mDb, mFinish, mDontSave).run();
|
||||
}
|
||||
|
||||
private class AfterUpdate extends OnFinish {
|
||||
private PwGroup mBackup;
|
||||
|
||||
AfterUpdate(PwGroup backup, OnFinish finish) {
|
||||
super(finish);
|
||||
mBackup = backup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ( !mSuccess ) {
|
||||
// If we fail to save, back out changes to global structure
|
||||
mDb.updateGroup(mOldGroup, mBackup);
|
||||
}
|
||||
|
||||
// TODO Better callback
|
||||
AfterActionNodeOnFinish afterActionNodeOnFinish =
|
||||
(AfterActionNodeOnFinish) super.mOnFinish;
|
||||
afterActionNodeOnFinish.mSuccess = mSuccess;
|
||||
afterActionNodeOnFinish.mMessage = mMessage;
|
||||
afterActionNodeOnFinish.run(mOldGroup, mNewGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import android.widget.Toast;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator;
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||
import com.kunzisoft.keepass.utils.Util;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@@ -46,6 +47,7 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
private GeneratePasswordListener mListener;
|
||||
private View root;
|
||||
private EditText lengthTextView;
|
||||
private EditText passwordView;
|
||||
|
||||
private CompoundButton uppercaseBox;
|
||||
private CompoundButton lowercaseBox;
|
||||
@@ -75,6 +77,9 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
root = inflater.inflate(R.layout.generate_password, null);
|
||||
|
||||
passwordView = root.findViewById(R.id.password);
|
||||
Util.applyFontVisibilityTo(getContext(), passwordView);
|
||||
|
||||
lengthTextView = root.findViewById(R.id.length);
|
||||
|
||||
uppercaseBox = root.findViewById(R.id.cb_uppercase);
|
||||
@@ -109,9 +114,8 @@ public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
|
||||
builder.setView(root)
|
||||
.setPositiveButton(R.string.accept, (dialog, id) -> {
|
||||
EditText password = root.findViewById(R.id.password);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_PASSWORD_ID, password.getText().toString());
|
||||
bundle.putString(KEY_PASSWORD_ID, passwordView.getText().toString());
|
||||
mListener.acceptPassword(bundle);
|
||||
|
||||
dismiss();
|
||||
|
||||
@@ -21,41 +21,66 @@ package com.kunzisoft.keepass.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
import com.kunzisoft.keepass.database.PwIcon;
|
||||
import com.kunzisoft.keepass.database.PwNode;
|
||||
import com.kunzisoft.keepass.icons.Icons;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
|
||||
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION;
|
||||
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE;
|
||||
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.getActionFromOrdinal;
|
||||
|
||||
public class GroupEditDialogFragment extends DialogFragment
|
||||
implements IconPickerDialogFragment.IconPickerListener {
|
||||
|
||||
public static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP";
|
||||
|
||||
public static final String KEY_NAME = "name";
|
||||
public static final String KEY_ICON_ID = "icon_id";
|
||||
public static final String KEY_NAME = "KEY_NAME";
|
||||
public static final String KEY_ICON_ID = "KEY_ICON_ID";
|
||||
public static final String KEY_ACTION_ID = "KEY_ACTION_ID";
|
||||
|
||||
private EditGroupListener editGroupListener;
|
||||
|
||||
private TextView nameField;
|
||||
private ImageButton iconButton;
|
||||
private int mSelectedIconID;
|
||||
private View root;
|
||||
private EditGroupDialogAction editGroupDialogAction;
|
||||
private String nameGroup;
|
||||
private PwIcon iconGroup;
|
||||
|
||||
private ImageView iconButton;
|
||||
|
||||
public enum EditGroupDialogAction {
|
||||
CREATION, UPDATE, NONE;
|
||||
|
||||
public static EditGroupDialogAction getActionFromOrdinal(int ordinal) {
|
||||
return EditGroupDialogAction.values()[ordinal];
|
||||
}
|
||||
}
|
||||
|
||||
public static GroupEditDialogFragment build() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(KEY_ACTION_ID, CREATION.ordinal());
|
||||
GroupEditDialogFragment fragment = new GroupEditDialogFragment();
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public static GroupEditDialogFragment build(PwNode group) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_NAME, group.getDisplayTitle());
|
||||
// TODO Change
|
||||
bundle.putInt(KEY_ICON_ID, group.getIcon().hashCode());
|
||||
bundle.putSerializable(KEY_ICON_ID, group.getIcon());
|
||||
bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal());
|
||||
GroupEditDialogFragment fragment = new GroupEditDialogFragment();
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
@@ -78,70 +103,115 @@ public class GroupEditDialogFragment extends DialogFragment
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
|
||||
assert getActivity() != null;
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
root = inflater.inflate(R.layout.group_edit, null);
|
||||
nameField = (TextView) root.findViewById(R.id.group_name);
|
||||
iconButton = (ImageButton) root.findViewById(R.id.icon_button);
|
||||
View root = inflater.inflate(R.layout.group_edit, null);
|
||||
TextView nameField = root.findViewById(R.id.group_name);
|
||||
iconButton = root.findViewById(R.id.icon_button);
|
||||
|
||||
if (getArguments() != null
|
||||
&& getArguments().containsKey(KEY_NAME)
|
||||
&& getArguments().containsKey(KEY_ICON_ID)) {
|
||||
nameField.setText(getArguments().getString(KEY_NAME));
|
||||
populateIcon(getArguments().getInt(KEY_ICON_ID));
|
||||
// Retrieve the textColor to tint the icon
|
||||
int[] attrs = {android.R.attr.textColorPrimary};
|
||||
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(attrs);
|
||||
int iconColor = ta.getColor(0, Color.WHITE);
|
||||
|
||||
// Init elements
|
||||
editGroupDialogAction = EditGroupDialogAction.NONE;
|
||||
nameGroup = "";
|
||||
iconGroup = App.getDB().getPwDatabase().getIconFactory().getFirstIcon();
|
||||
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(KEY_ACTION_ID)
|
||||
&& savedInstanceState.containsKey(KEY_NAME)
|
||||
&& savedInstanceState.containsKey(KEY_ICON_ID)) {
|
||||
editGroupDialogAction = getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID));
|
||||
nameGroup = savedInstanceState.getString(KEY_NAME);
|
||||
iconGroup = (PwIcon) savedInstanceState.getSerializable(KEY_ICON_ID);
|
||||
|
||||
} else {
|
||||
|
||||
if (getArguments() != null
|
||||
&& getArguments().containsKey(KEY_ACTION_ID))
|
||||
editGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(getArguments().getInt(KEY_ACTION_ID));
|
||||
|
||||
if (getArguments() != null
|
||||
&& getArguments().containsKey(KEY_NAME)
|
||||
&& getArguments().containsKey(KEY_ICON_ID)) {
|
||||
nameGroup = getArguments().getString(KEY_NAME);
|
||||
iconGroup = (PwIcon) getArguments().getSerializable(KEY_ICON_ID);
|
||||
}
|
||||
}
|
||||
|
||||
// populate the name
|
||||
nameField.setText(nameGroup);
|
||||
// populate the icon
|
||||
if (IconPackChooser.getSelectedIconPack(getContext()).tintable()) {
|
||||
App.getDB().getDrawFactory()
|
||||
.assignDatabaseIconTo(
|
||||
getContext(),
|
||||
iconButton,
|
||||
iconGroup,
|
||||
true,
|
||||
iconColor);
|
||||
} else {
|
||||
App.getDB().getDrawFactory()
|
||||
.assignDatabaseIconTo(
|
||||
getContext(),
|
||||
iconButton,
|
||||
iconGroup);
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setView(root)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
String name = nameField.getText().toString();
|
||||
if ( name.length() > 0 ) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_NAME, name);
|
||||
bundle.putInt(KEY_ICON_ID, mSelectedIconID);
|
||||
editGroupListener.approveEditGroup(bundle);
|
||||
|
||||
GroupEditDialogFragment.this.getDialog().cancel();
|
||||
}
|
||||
else {
|
||||
Toast.makeText(getContext(), R.string.error_no_name, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Bundle bundle = new Bundle();
|
||||
editGroupListener.cancelEditGroup(bundle);
|
||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
||||
String name = nameField.getText().toString();
|
||||
if ( name.length() > 0 ) {
|
||||
editGroupListener.approveEditGroup(
|
||||
editGroupDialogAction,
|
||||
name,
|
||||
iconGroup);
|
||||
|
||||
GroupEditDialogFragment.this.getDialog().cancel();
|
||||
}
|
||||
else {
|
||||
Toast.makeText(getContext(), R.string.error_no_name, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, id) -> {
|
||||
String name = nameField.getText().toString();
|
||||
editGroupListener.cancelEditGroup(
|
||||
editGroupDialogAction,
|
||||
name,
|
||||
iconGroup);
|
||||
|
||||
GroupEditDialogFragment.this.getDialog().cancel();
|
||||
});
|
||||
|
||||
final ImageButton iconButton = (ImageButton) root.findViewById(R.id.icon_button);
|
||||
iconButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
IconPickerDialogFragment iconPickerDialogFragment = new IconPickerDialogFragment();
|
||||
iconButton.setOnClickListener(v -> {
|
||||
IconPickerDialogFragment iconPickerDialogFragment = new IconPickerDialogFragment();
|
||||
if (getFragmentManager() != null)
|
||||
iconPickerDialogFragment.show(getFragmentManager(), "IconPickerDialogFragment");
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private void populateIcon(int iconId) {
|
||||
iconButton.setImageResource(Icons.iconToResId(iconId));
|
||||
@Override
|
||||
public void iconPicked(Bundle bundle) {
|
||||
int selectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
|
||||
iconButton.setImageResource(IconPackChooser.getSelectedIconPack(getContext()).iconToResId(selectedIconID));
|
||||
iconGroup = App.getDB().getPwDatabase().getIconFactory().getIcon(selectedIconID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void iconPicked(Bundle bundle) {
|
||||
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
|
||||
populateIcon(mSelectedIconID);
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putInt(KEY_ACTION_ID, editGroupDialogAction.ordinal());
|
||||
outState.putString(KEY_NAME, nameGroup);
|
||||
outState.putSerializable(KEY_ICON_ID, iconGroup);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
public interface EditGroupListener {
|
||||
void approveEditGroup(Bundle bundle);
|
||||
void cancelEditGroup(Bundle bundle);
|
||||
void approveEditGroup(EditGroupDialogAction action, String name, PwIcon selectedIcon);
|
||||
void cancelEditGroup(EditGroupDialogAction action, String name, PwIcon selectedIcon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,34 +21,37 @@ package com.kunzisoft.keepass.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.widget.ImageViewCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.icons.Icons;
|
||||
import com.kunzisoft.keepass.icons.IconPack;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
import com.kunzisoft.keepass.stylish.StylishActivity;
|
||||
|
||||
|
||||
public class IconPickerDialogFragment extends DialogFragment {
|
||||
public static final String KEY_ICON_ID = "icon_id";
|
||||
public static final int UNDEFINED_ICON_ID = -1;
|
||||
private IconPickerListener iconPickerListener;
|
||||
private IconPack iconPack;
|
||||
|
||||
public static void launch(StylishActivity activity) {
|
||||
// Create an instance of the dialog fragment and show it
|
||||
IconPickerDialogFragment dialog = new IconPickerDialogFragment();
|
||||
dialog.show(activity.getSupportFragmentManager(), "NoticeDialogFragment");
|
||||
dialog.show(activity.getSupportFragmentManager(), "IconPickerDialogFragment");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,52 +73,46 @@ public class IconPickerDialogFragment extends DialogFragment {
|
||||
// Get the layout inflater
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
|
||||
iconPack = IconPackChooser.getSelectedIconPack(getContext());
|
||||
|
||||
// Inflate and set the layout for the dialog
|
||||
// Pass null as the parent view because its going in the dialog layout
|
||||
View root = inflater.inflate(R.layout.icon_picker, null);
|
||||
builder.setView(root);
|
||||
|
||||
GridView currIconGridView = (GridView) root.findViewById(R.id.IconGridView);
|
||||
GridView currIconGridView = root.findViewById(R.id.IconGridView);
|
||||
currIconGridView.setAdapter(new ImageAdapter(this.getContext()));
|
||||
|
||||
currIconGridView.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(KEY_ICON_ID, position);
|
||||
iconPickerListener.iconPicked(bundle);
|
||||
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
IconPickerDialogFragment.this.getDialog().cancel();
|
||||
}
|
||||
currIconGridView.setOnItemClickListener((parent, v, position, id) -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(KEY_ICON_ID, position);
|
||||
iconPickerListener.iconPicked(bundle);
|
||||
dismiss();
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.cancel, (dialog, id) ->
|
||||
IconPickerDialogFragment.this.getDialog().cancel());
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public class ImageAdapter extends BaseAdapter {
|
||||
private Context context;
|
||||
|
||||
public ImageAdapter(Context c) {
|
||||
ImageAdapter(Context c) {
|
||||
context = c;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
public int getCount() {
|
||||
/* Return number of KeePass icons */
|
||||
return Icons.count();
|
||||
return iconPack.numberOfIcons();
|
||||
}
|
||||
|
||||
public Object getItem(int position)
|
||||
{
|
||||
public Object getItem(int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getItemId(int position)
|
||||
{
|
||||
public long getItemId(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -123,16 +120,24 @@ public class IconPickerDialogFragment extends DialogFragment {
|
||||
View currView;
|
||||
if(convertView == null) {
|
||||
LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
currView = li.inflate(R.layout.icon, parent, false);
|
||||
assert li != null;
|
||||
currView = li.inflate(R.layout.icon, parent, false);
|
||||
}
|
||||
else {
|
||||
currView = convertView;
|
||||
}
|
||||
ImageView iv = currView.findViewById(R.id.icon_image);
|
||||
iv.setImageResource(iconPack.iconToResId(position));
|
||||
|
||||
TextView tv = (TextView) currView.findViewById(R.id.icon_text);
|
||||
tv.setText("" + position);
|
||||
ImageView iv = (ImageView) currView.findViewById(R.id.icon_image);
|
||||
iv.setImageResource(Icons.iconToResId(position));
|
||||
// Assign color if icons are tintable
|
||||
if (iconPack.tintable()) {
|
||||
// Retrieve the textColor to tint the icon
|
||||
int[] attrs = {android.R.attr.textColor};
|
||||
assert getContext() != null;
|
||||
TypedArray ta = getContext().getTheme().obtainStyledAttributes(attrs);
|
||||
int iconColor = ta.getColor(0, Color.BLACK);
|
||||
ImageViewCompat.setImageTintList(iv, ColorStateList.valueOf(iconColor));
|
||||
}
|
||||
|
||||
return currView;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.BuildConfig;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.utils.Util;
|
||||
|
||||
/**
|
||||
* Custom Dialog that asks the user to download the pro version or make a donation.
|
||||
*/
|
||||
public class ProFeatureDialogFragment extends DialogFragment {
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
// Use the Builder class for convenient dialog construction
|
||||
assert getActivity() != null;
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_ad_free))).append("\n\n");
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_buy_pro)));
|
||||
builder.setPositiveButton(R.string.download, (dialog, id) -> {
|
||||
try {
|
||||
Util.gotoUrl(getContext(), R.string.app_pro_url);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getContext(), R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_feature_generosity))).append("\n\n");
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_donation)));
|
||||
builder.setPositiveButton(R.string.contribute, (dialog, id) -> {
|
||||
try {
|
||||
Util.gotoUrl(getContext(), R.string.donate_url);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getContext(), R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
builder.setMessage(stringBuilder);
|
||||
builder.setNegativeButton(android.R.string.cancel, (dialog, id) -> dismiss());
|
||||
// Create the AlertDialog object and return it
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.BuildConfig;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.utils.Util;
|
||||
|
||||
/**
|
||||
* Custom Dialog that asks the user to download the pro version or make a donation.
|
||||
*/
|
||||
public class UnderDevelopmentFeatureDialogFragment extends DialogFragment {
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
// Use the Builder class for convenient dialog construction
|
||||
assert getActivity() != null;
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n");
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature_buy_pro))).append(" ")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)));
|
||||
builder.setPositiveButton(R.string.download, (dialog, id) -> {
|
||||
try {
|
||||
Util.gotoUrl(getContext(), R.string.app_pro_url);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getContext(), R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature_contibute))).append(" ")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)));
|
||||
builder.setPositiveButton(R.string.contribute, (dialog, id) -> {
|
||||
try {
|
||||
Util.gotoUrl(getContext(), R.string.donate_url);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getContext(), R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
builder.setMessage(stringBuilder);
|
||||
builder.setNegativeButton(android.R.string.cancel, (dialog, id) -> dismiss());
|
||||
// Create the AlertDialog object and return it
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,9 @@ import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.stylish.Stylish;
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||
|
||||
|
||||
/**
|
||||
* FilePickerActivity class with a style compatibility
|
||||
*/
|
||||
public class FilePickerStylishActivity extends FilePickerActivity {
|
||||
|
||||
private @StyleRes
|
||||
@@ -51,12 +53,19 @@ public class FilePickerStylishActivity extends FilePickerActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived from the Stylish class, get the specific FilePickerStyle theme
|
||||
*/
|
||||
public static class FilePickerStylish extends Stylish {
|
||||
public static @StyleRes int getThemeId(Context context) {
|
||||
if (themeString.equals(context.getString(R.string.list_style_name_night)))
|
||||
return R.style.KeepassDXStyle_FilePickerStyle_Night;
|
||||
if (themeString.equals(context.getString(R.string.list_style_name_dark)))
|
||||
return R.style.KeepassDXStyle_FilePickerStyle_Dark;
|
||||
else if (themeString.equals(context.getString(R.string.list_style_name_blue)))
|
||||
return R.style.KeepassDXStyle_FilePickerStyle_Blue;
|
||||
else if (themeString.equals(context.getString(R.string.list_style_name_purple)))
|
||||
return R.style.KeepassDXStyle_FilePickerStyle_Purple;
|
||||
|
||||
return R.style.KeepassDXStyle_FilePickerStyle_Light;
|
||||
return R.style.KeepassDXStyle_FilePickerStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.os.Environment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -40,8 +41,11 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.activities.GroupActivity;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
@@ -59,7 +63,8 @@ import com.kunzisoft.keepass.tasks.ProgressTask;
|
||||
import com.kunzisoft.keepass.utils.EmptyUtils;
|
||||
import com.kunzisoft.keepass.utils.MenuUtil;
|
||||
import com.kunzisoft.keepass.utils.UriUtil;
|
||||
import com.kunzisoft.keepass.view.FileNameView;
|
||||
|
||||
import net.cachapa.expandablelayout.ExpandableLayout;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -87,6 +92,9 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
|
||||
private FileSelectAdapter mAdapter;
|
||||
private View fileListTitle;
|
||||
private View createButtonView;
|
||||
private View browseButtonView;
|
||||
private View openButtonView;
|
||||
|
||||
private RecentFileHistory fileHistory;
|
||||
|
||||
@@ -94,8 +102,9 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
private boolean consultationMode = false;
|
||||
private AutofillHelper autofillHelper;
|
||||
|
||||
private View fileSelectExpandableButton;
|
||||
private ExpandableLayout fileSelectExpandable;
|
||||
private EditText openFileNameView;
|
||||
private FileNameView fileNameView;
|
||||
|
||||
private AssignPasswordHelper assignPasswordHelper;
|
||||
private Uri databaseUri;
|
||||
@@ -136,11 +145,10 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
fileListTitle = findViewById(R.id.file_list_title);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
toolbar.setTitle("");
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
openFileNameView = findViewById(R.id.file_filename);
|
||||
fileNameView = findViewById(R.id.file_select);
|
||||
|
||||
// Set the initial value of the filename
|
||||
defaultPath = Environment.getExternalStorageDirectory().getAbsolutePath()
|
||||
@@ -149,6 +157,17 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
+ getString(R.string.database_file_extension_default);
|
||||
openFileNameView.setHint(R.string.open_link_database);
|
||||
|
||||
// Button to expand file selection
|
||||
fileSelectExpandableButton = findViewById(R.id.file_select_expandable_button);
|
||||
fileSelectExpandable = findViewById(R.id.file_select_expandable);
|
||||
fileSelectExpandableButton.setOnClickListener(view -> {
|
||||
if (fileSelectExpandable.isExpanded())
|
||||
fileSelectExpandable.collapse();
|
||||
else
|
||||
fileSelectExpandable.expand();
|
||||
});
|
||||
|
||||
// History list
|
||||
RecyclerView mListFiles = findViewById(R.id.file_list);
|
||||
mListFiles.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
@@ -159,8 +178,8 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
}
|
||||
|
||||
// Open button
|
||||
View openButton = findViewById(R.id.open_database);
|
||||
openButton.setOnClickListener(v -> {
|
||||
openButtonView = findViewById(R.id.open_database);
|
||||
openButtonView.setOnClickListener(v -> {
|
||||
String fileName = openFileNameView.getText().toString();
|
||||
if (fileName.isEmpty())
|
||||
fileName = defaultPath;
|
||||
@@ -168,15 +187,15 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
});
|
||||
|
||||
// Create button
|
||||
View createButton = findViewById(R.id.create_database);
|
||||
createButton.setOnClickListener(v ->
|
||||
createButtonView = findViewById(R.id.create_database);
|
||||
createButtonView .setOnClickListener(v ->
|
||||
FileSelectActivityPermissionsDispatcher
|
||||
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this)
|
||||
);
|
||||
|
||||
keyFileHelper = new KeyFileHelper(this);
|
||||
View browseButton = findViewById(R.id.browse_button);
|
||||
browseButton.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener(
|
||||
browseButtonView = findViewById(R.id.browse_button);
|
||||
browseButtonView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener(
|
||||
() -> Uri.parse("file://" + openFileNameView.getText().toString())));
|
||||
|
||||
// Construct adapter with listeners
|
||||
@@ -212,6 +231,9 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For the first time show education
|
||||
checkAndPerformedEducation();
|
||||
}
|
||||
|
||||
private void launchPasswordActivityWithPath(String path) {
|
||||
@@ -246,15 +268,136 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
private void updateExternalStorageWarning() {
|
||||
// To show errors
|
||||
int warning = -1;
|
||||
String state = Environment.getExternalStorageState();
|
||||
if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
|
||||
warning = R.string.warning_read_only;
|
||||
} else if (!state.equals(Environment.MEDIA_MOUNTED)) {
|
||||
warning = R.string.warning_unmounted;
|
||||
}
|
||||
|
||||
TextView labelWarningView = findViewById(R.id.label_warning);
|
||||
if (warning != -1) {
|
||||
labelWarningView.setText(warning);
|
||||
labelWarningView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
labelWarningView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
fileNameView.updateExternalStorageWarning();
|
||||
updateExternalStorageWarning();
|
||||
updateTitleFileListView();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and display learning views
|
||||
* Displays the explanation for a database creation then a database selection
|
||||
*/
|
||||
private void checkAndPerformedEducation() {
|
||||
|
||||
// If no recent files
|
||||
if ( !fileHistory.hasRecentFiles() ) {
|
||||
// Try to open the creation base education
|
||||
if (!PreferencesUtil.isEducationCreateDatabasePerformed(this) ) {
|
||||
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forView(createButtonView,
|
||||
getString(R.string.education_create_database_title),
|
||||
getString(R.string.education_create_database_summary))
|
||||
.icon(ContextCompat.getDrawable(this, R.drawable.ic_database_plus_white_24dp))
|
||||
.tintTarget(true)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
FileSelectActivityPermissionsDispatcher
|
||||
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
// But if the user cancel, it can also select a database
|
||||
checkAndPerformedEducationForSelection();
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(FileSelectActivity.this,
|
||||
R.string.education_create_db_key);
|
||||
}
|
||||
}
|
||||
else
|
||||
checkAndPerformedEducationForSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and display learning views
|
||||
* Displays the explanation for a database selection
|
||||
*/
|
||||
private void checkAndPerformedEducationForSelection() {
|
||||
|
||||
if (!PreferencesUtil.isEducationSelectDatabasePerformed(this)
|
||||
&& browseButtonView != null) {
|
||||
|
||||
TapTargetView.showFor(FileSelectActivity.this,
|
||||
TapTarget.forView(browseButtonView,
|
||||
getString(R.string.education_select_database_title),
|
||||
getString(R.string.education_select_database_summary))
|
||||
.icon(ContextCompat.getDrawable(this, R.drawable.ic_folder_white_24dp))
|
||||
.tintTarget(true)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
keyFileHelper.getOpenFileOnClickViewListener().onClick(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
|
||||
if (!PreferencesUtil.isEducationOpenLinkDatabasePerformed(FileSelectActivity.this)) {
|
||||
|
||||
TapTargetView.showFor(FileSelectActivity.this,
|
||||
TapTarget.forView(fileSelectExpandableButton,
|
||||
getString(R.string.education_open_link_database_title),
|
||||
getString(R.string.education_open_link_database_summary))
|
||||
.icon(ContextCompat.getDrawable(FileSelectActivity.this, R.drawable.ic_link_white_24dp))
|
||||
.tintTarget(true)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(FileSelectActivity.this,
|
||||
R.string.education_open_link_db_key);
|
||||
}
|
||||
}
|
||||
});
|
||||
PreferencesUtil.saveEducationPreference(FileSelectActivity.this,
|
||||
R.string.education_select_db_key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -381,9 +524,10 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
FileSelectActivity.this, create,
|
||||
R.string.progress_create);
|
||||
createTask.run();
|
||||
assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPassword, keyFile);
|
||||
|
||||
assignPasswordHelper = new AssignPasswordHelper(this,
|
||||
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
|
||||
|
||||
} catch (Exception e) {
|
||||
String error = "Unable to create database with this password and key file";
|
||||
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
|
||||
@@ -494,6 +638,7 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
if (PreferencesUtil.autoOpenSelectedFile(FileSelectActivity.this)) {
|
||||
launchPasswordActivityWithPath(uri.toString());
|
||||
} else {
|
||||
fileSelectExpandable.expand(false);
|
||||
openFileNameView.setText(uri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(R.attr.colorAccentCompat, typedValue, true);
|
||||
warningColor = typedValue.data;
|
||||
theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
|
||||
theme.resolveAttribute(android.R.attr.textColorHintInverse, typedValue, true);
|
||||
defaultColor = typedValue.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.icons;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.compat.BitmapDrawableCompat;
|
||||
import com.kunzisoft.keepass.database.PwIcon;
|
||||
import com.kunzisoft.keepass.database.PwIconCustom;
|
||||
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||
|
||||
import org.apache.commons.collections.map.AbstractReferenceMap;
|
||||
import org.apache.commons.collections.map.ReferenceMap;
|
||||
|
||||
public class DrawableFactory {
|
||||
private static Drawable blank = null;
|
||||
private static int blankWidth = -1;
|
||||
private static int blankHeight = -1;
|
||||
|
||||
/** customIconMap
|
||||
* Cache for icon drawable.
|
||||
* Keys: UUID, Values: Drawables
|
||||
*/
|
||||
private ReferenceMap customIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
||||
|
||||
/** standardIconMap
|
||||
* Cache for icon drawable.
|
||||
* Keys: Integer, Values: Drawables
|
||||
*/
|
||||
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
||||
|
||||
public void assignDrawableTo(ImageView iv, Resources res, PwIcon icon) {
|
||||
Drawable draw = getIconDrawable(res, icon);
|
||||
if (iv != null && draw != null)
|
||||
iv.setImageDrawable(draw);
|
||||
}
|
||||
|
||||
public Drawable getIconDrawable(Resources res, PwIcon icon) {
|
||||
if (icon instanceof PwIconStandard) {
|
||||
return getIconDrawable(res, (PwIconStandard) icon);
|
||||
} else {
|
||||
return getIconDrawable(res, (PwIconCustom) icon);
|
||||
}
|
||||
}
|
||||
|
||||
private static void initBlank(Resources res) {
|
||||
if (blank==null) {
|
||||
blank = res.getDrawable(R.drawable.ic99_blank);
|
||||
blankWidth = blank.getIntrinsicWidth();
|
||||
blankHeight = blank.getIntrinsicHeight();
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable getIconDrawable(Resources res, PwIconStandard icon) {
|
||||
int resId = Icons.iconToResId(icon.iconId);
|
||||
|
||||
Drawable draw = (Drawable) standardIconMap.get(resId);
|
||||
if (draw == null) {
|
||||
draw = res.getDrawable(resId);
|
||||
standardIconMap.put(resId, draw);
|
||||
}
|
||||
|
||||
return draw;
|
||||
}
|
||||
|
||||
public Drawable getIconDrawable(Resources res, PwIconCustom icon) {
|
||||
initBlank(res);
|
||||
if (icon == null) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
Drawable draw = (Drawable) customIconMap.get(icon.uuid);
|
||||
|
||||
if (draw == null) {
|
||||
if (icon.imageData == null) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.length);
|
||||
|
||||
// Could not understand custom icon
|
||||
if (bitmap == null) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
bitmap = resize(bitmap);
|
||||
|
||||
draw = BitmapDrawableCompat.getBitmapDrawable(res, bitmap);
|
||||
customIconMap.put(icon.uuid, draw);
|
||||
}
|
||||
|
||||
return draw;
|
||||
}
|
||||
|
||||
/** Resize the custom icon to match the built in icons
|
||||
* @param bitmap
|
||||
* @return
|
||||
*/
|
||||
private Bitmap resize(Bitmap bitmap) {
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
|
||||
if (width == blankWidth && height == blankHeight) {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
return Bitmap.createScaledBitmap(bitmap, blankWidth, blankHeight, true);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
standardIconMap.clear();
|
||||
customIconMap.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.icons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.widget.ImageViewCompat;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.compat.BitmapDrawableCompat;
|
||||
import com.kunzisoft.keepass.database.PwIcon;
|
||||
import com.kunzisoft.keepass.database.PwIconCustom;
|
||||
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||
|
||||
import org.apache.commons.collections.map.AbstractReferenceMap;
|
||||
import org.apache.commons.collections.map.ReferenceMap;
|
||||
|
||||
/**
|
||||
* Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint
|
||||
*/
|
||||
public class IconDrawableFactory {
|
||||
private static Drawable blank = null;
|
||||
private static int blankWidth = -1;
|
||||
private static int blankHeight = -1;
|
||||
|
||||
/** customIconMap
|
||||
* Cache for icon drawable.
|
||||
* Keys: UUID, Values: Drawables
|
||||
*/
|
||||
private ReferenceMap customIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
||||
|
||||
/** standardIconMap
|
||||
* Cache for icon drawable.
|
||||
* Keys: Integer, Values: Drawables
|
||||
*/
|
||||
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
||||
|
||||
/**
|
||||
* Assign a default database icon to an ImageView
|
||||
*
|
||||
* @param context Context to build the drawable
|
||||
* @param iv ImageView that will host the drawable
|
||||
*/
|
||||
public void assignDefaultDatabaseIconTo(Context context, ImageView iv) {
|
||||
assignDefaultDatabaseIconTo(context, iv, false, Color.WHITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a default database icon to an ImageView and tint it
|
||||
*
|
||||
* @param context Context to build the drawable
|
||||
* @param iv ImageView that will host the drawable
|
||||
*/
|
||||
public void assignDefaultDatabaseIconTo(Context context, ImageView iv, boolean tint, int tintColor) {
|
||||
assignDrawableTo(context, iv, IconPackChooser.getSelectedIconPack(context).getDefaultIconId(), tint, tintColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a database icon to an ImageView
|
||||
*
|
||||
* @param context Context to build the drawable
|
||||
* @param iv ImageView that will host the drawable
|
||||
* @param icon The icon from the database
|
||||
*/
|
||||
public void assignDatabaseIconTo(Context context, ImageView iv, PwIcon icon) {
|
||||
assignDatabaseIconTo(context, iv, icon, false, Color.WHITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a database icon to an ImageView and tint it
|
||||
*
|
||||
* @param context Context to build the drawable
|
||||
* @param imageView ImageView that will host the drawable
|
||||
* @param icon The icon from the database
|
||||
* @param tint true will tint the drawable with tintColor
|
||||
* @param tintColor Use this color if tint is true
|
||||
*/
|
||||
public void assignDatabaseIconTo(Context context, ImageView imageView, PwIcon icon, boolean tint, int tintColor) {
|
||||
assignDrawableToImageView(getIconDrawable(context, icon, tint, tintColor),
|
||||
imageView,
|
||||
tint,
|
||||
tintColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an image by its resourceId to an ImageView and tint it
|
||||
*
|
||||
* @param context Context to build the drawable
|
||||
* @param imageView ImageView that will host the drawable
|
||||
* @param iconId iconId from the resources
|
||||
* @param tint true will tint the drawable with tintColor
|
||||
* @param tintColor Use this color if tint is true
|
||||
*/
|
||||
public void assignDrawableTo(Context context, ImageView imageView, int iconId, boolean tint, int tintColor) {
|
||||
assignDrawableToImageView(new SuperDrawable(getIconDrawable(context, iconId, tint, tintColor)),
|
||||
imageView,
|
||||
tint,
|
||||
tintColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to assign a drawable to an ImageView and tint it
|
||||
*/
|
||||
private void assignDrawableToImageView(SuperDrawable superDrawable, ImageView imageView, boolean tint, int tintColor) {
|
||||
if (imageView != null && superDrawable.drawable != null) {
|
||||
imageView.setImageDrawable(superDrawable.drawable);
|
||||
if (!superDrawable.custom && tint) {
|
||||
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(tintColor));
|
||||
} else {
|
||||
ImageViewCompat.setImageTintList(imageView, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the drawable icon from cache or build it and add it to the cache if not exists yet
|
||||
*
|
||||
* @param context Context to build the drawable
|
||||
* @param icon The icon from database
|
||||
* @return The build drawable
|
||||
*/
|
||||
public Drawable getIconDrawable(Context context, PwIcon icon) {
|
||||
return getIconDrawable(context, icon, false, Color.WHITE).drawable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the drawable icon from cache or build it and add it to the cache if not exists yet then tint it if needed
|
||||
*
|
||||
* @param context Context to build the drawable
|
||||
* @param icon The icon from database
|
||||
* @param tint true will tint the drawable with tintColor
|
||||
* @param tintColor Use this color if tint is true
|
||||
* @return The build drawable
|
||||
*/
|
||||
public SuperDrawable getIconDrawable(Context context, PwIcon icon, boolean tint, int tintColor) {
|
||||
if (icon instanceof PwIconStandard) {
|
||||
return new SuperDrawable(getIconDrawable(context.getApplicationContext(), (PwIconStandard) icon, tint, tintColor));
|
||||
} else {
|
||||
return new SuperDrawable(getIconDrawable(context, (PwIconCustom) icon), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a blank drawable
|
||||
* @param res Resource to build the drawable
|
||||
*/
|
||||
private static void initBlank(Resources res) {
|
||||
if (blank==null) {
|
||||
blankWidth = (int) res.getDimension(R.dimen.icon_size);
|
||||
blankHeight = (int) res.getDimension(R.dimen.icon_size);
|
||||
blank = new ColorDrawable(Color.TRANSPARENT);
|
||||
blank.setBounds(0, 0, blankWidth, blankHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key class to retrieve a Drawable in the cache if it's tinted or not
|
||||
*/
|
||||
private class CacheKey {
|
||||
int resId;
|
||||
boolean isTint;
|
||||
int color;
|
||||
|
||||
CacheKey(int resId, boolean isTint, int color) {
|
||||
this.resId = resId;
|
||||
this.isTint = isTint;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CacheKey cacheKey = (CacheKey) o;
|
||||
if (isTint)
|
||||
return resId == cacheKey.resId &&
|
||||
cacheKey.isTint &&
|
||||
color == cacheKey.color;
|
||||
else
|
||||
return resId == cacheKey.resId &&
|
||||
!cacheKey.isTint;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the drawable icon from cache or build it and add it to the cache if not exists yet
|
||||
*
|
||||
* @param context Context to make drawable
|
||||
* @param icon Icon from database
|
||||
* @param isTint Tint the drawable if true
|
||||
* @param tintColor Use this color if tint is true
|
||||
* @return The drawable
|
||||
*/
|
||||
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
|
||||
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.iconId);
|
||||
|
||||
return getIconDrawable(context, resId, isTint, tintColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the drawable icon from cache or build it and add it to the cache if not exists yet
|
||||
*
|
||||
* @param context Context to make drawable
|
||||
* @param iconId iconId from resources
|
||||
* @param isTint Tint the drawable if true
|
||||
* @param tintColor Use this color if tint is true
|
||||
* @return The drawable
|
||||
*/
|
||||
private Drawable getIconDrawable(Context context, int iconId, boolean isTint, int tintColor) {
|
||||
CacheKey newCacheKey = new CacheKey(iconId, isTint, tintColor);
|
||||
|
||||
Drawable draw = (Drawable) standardIconMap.get(newCacheKey);
|
||||
if (draw == null) {
|
||||
draw = ContextCompat.getDrawable(context, iconId);
|
||||
if (draw != null) {
|
||||
standardIconMap.put(newCacheKey, draw);
|
||||
}
|
||||
}
|
||||
|
||||
return draw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to prevent a custom icon to be tint
|
||||
*/
|
||||
private class SuperDrawable {
|
||||
Drawable drawable;
|
||||
boolean custom;
|
||||
|
||||
SuperDrawable(Drawable drawable) {
|
||||
this.drawable = drawable;
|
||||
this.custom = false;
|
||||
}
|
||||
|
||||
SuperDrawable(Drawable drawable, boolean custom) {
|
||||
this.drawable = drawable;
|
||||
this.custom = custom;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a custom icon from database
|
||||
* @param context Context to build the drawable
|
||||
* @param icon Icon from database
|
||||
* @return The drawable
|
||||
*/
|
||||
private Drawable getIconDrawable(Context context, PwIconCustom icon) {
|
||||
initBlank(context.getResources());
|
||||
if (icon == null) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
Drawable draw = (Drawable) customIconMap.get(icon.uuid);
|
||||
|
||||
if (draw == null) {
|
||||
if (icon.imageData == null) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.length);
|
||||
|
||||
// Could not understand custom icon
|
||||
if (bitmap == null) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
bitmap = resize(bitmap);
|
||||
|
||||
draw = BitmapDrawableCompat.getBitmapDrawable(context.getResources(), bitmap);
|
||||
customIconMap.put(icon.uuid, draw);
|
||||
}
|
||||
|
||||
return draw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the custom icon to match the built in icons
|
||||
*
|
||||
* @param bitmap Bitmap to resize
|
||||
* @return Bitmap resized
|
||||
*/
|
||||
private Bitmap resize(Bitmap bitmap) {
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
|
||||
if (width == blankWidth && height == blankHeight) {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
return Bitmap.createScaledBitmap(bitmap, blankWidth, blankHeight, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache of icons
|
||||
*/
|
||||
public void clearCache() {
|
||||
standardIconMap.clear();
|
||||
customIconMap.clear();
|
||||
}
|
||||
|
||||
}
|
||||
159
app/src/main/java/com/kunzisoft/keepass/icons/IconPack.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.icons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Class who construct dynamically database icons contains in a separate library
|
||||
*
|
||||
* <p>It only supports icons with specific nomenclature <strong>[stringId]_[%2d]_32dp</strong>
|
||||
* where [stringId] contains in a string xml attribute with id <strong>resource_id</strong> and
|
||||
* [%2d] 2 numerical numbers between 00 and 68 included,
|
||||
* </p>
|
||||
* <p>See <i>icon-pack-classic</i> module as sample
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public class IconPack {
|
||||
|
||||
private static final int NB_ICONS = 68;
|
||||
|
||||
private SparseIntArray icons;
|
||||
private String resourceStringId;
|
||||
private String name;
|
||||
private boolean tintable;
|
||||
|
||||
private Resources resources;
|
||||
|
||||
/**
|
||||
* Construct dynamically the icon pack provide by the string resource id
|
||||
*
|
||||
* @param context Context of the app to retrieve the resources
|
||||
* @param resourceId String Id of the pack (ex : com.kunzisoft.keepass.icon.classic.R.string.resource_id)
|
||||
*/
|
||||
IconPack(Context context, int resourceId) {
|
||||
|
||||
resources = context.getResources();
|
||||
icons = new SparseIntArray();
|
||||
resourceStringId = context.getString(resourceId);
|
||||
// If finish with a _ remove it
|
||||
if (resourceStringId.lastIndexOf('_') == resourceStringId.length() - 1)
|
||||
resourceStringId = resourceStringId.substring(0, resourceStringId.length() -1);
|
||||
|
||||
// Build the list of icons
|
||||
int num = 0;
|
||||
while(num <= NB_ICONS) {
|
||||
// To construct the id with name_ic_XX_32dp (ex : classic_ic_08_32dp )
|
||||
int resId = resources.getIdentifier(
|
||||
resourceStringId + "_" + new DecimalFormat("00").format(num) + "_32dp",
|
||||
"drawable",
|
||||
context.getPackageName());
|
||||
icons.put(num, resId);
|
||||
num++;
|
||||
}
|
||||
// Get visual name
|
||||
name = resources.getString(
|
||||
resources.getIdentifier(
|
||||
resourceStringId + "_" + "name",
|
||||
"string",
|
||||
context.getPackageName()
|
||||
)
|
||||
);
|
||||
// If icons are tintable
|
||||
tintable = resources.getBoolean(
|
||||
resources.getIdentifier(
|
||||
resourceStringId + "_" + "tintable",
|
||||
"bool",
|
||||
context.getPackageName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the IconPack
|
||||
*
|
||||
* @return String visual name of the pack
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the IconPack
|
||||
*
|
||||
* @return String id of the pack
|
||||
*/
|
||||
public String getId() {
|
||||
return resourceStringId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if each icon in the pack can be tint
|
||||
*
|
||||
* @return true if icons are tintable
|
||||
*/
|
||||
public boolean tintable() {
|
||||
return tintable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of icons in this pack
|
||||
*
|
||||
* @return int Number of database icons
|
||||
*/
|
||||
public int numberOfIcons() {
|
||||
return icons.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon as a resourceId
|
||||
*
|
||||
* @param iconId Icon database Id of the icon to retrieve
|
||||
* @return int resourceId
|
||||
*/
|
||||
public int iconToResId(int iconId) {
|
||||
return icons.get(iconId, R.drawable.ic_blank_32dp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int Get the default icon resource id
|
||||
*/
|
||||
public int getDefaultIconId() {
|
||||
return iconToResId(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon as a drawable
|
||||
*
|
||||
* @param iconId Icon database Id of the icon to retrieve
|
||||
* @return int resourceId
|
||||
*/
|
||||
public Drawable getDrawable(int iconId) {
|
||||
return resources.getDrawable(iconToResId(iconId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.icons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.BuildConfig;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class to built and select an IconPack dynamically by libraries importation
|
||||
*
|
||||
* @author J-Jamet
|
||||
*/
|
||||
public class IconPackChooser {
|
||||
|
||||
private static final String TAG = IconPackChooser.class.getName();
|
||||
|
||||
private static List<IconPack> iconPackList = new ArrayList<>();
|
||||
private static IconPack iconPackSelected = null;
|
||||
|
||||
private static IconPackChooser sIconPackBuilder;
|
||||
|
||||
/**
|
||||
* IconPackChooser as singleton
|
||||
*/
|
||||
private IconPackChooser(){
|
||||
if (sIconPackBuilder != null){
|
||||
throw new RuntimeException("Use build() method to get the single instance of this class.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Built the icon pack chooser based on imports made in <i>build.gradle</i>
|
||||
*
|
||||
* <p>Dynamic import can be done for each flavor by prefixing the 'implementation' command with the name of the flavor.< br/>
|
||||
* (ex : {@code libreImplementation project(path: ':icon-pack-classic')} <br />
|
||||
* Each name of icon pack must be in {@code ICON_PACKS} in the build.gradle file</p>
|
||||
*
|
||||
* @param context Context to construct each pack with the resources
|
||||
* @return An unique instance of {@link IconPackChooser}, recall {@link #build(Context)} provide the same instance
|
||||
*/
|
||||
@SuppressWarnings("JavaDoc")
|
||||
public static IconPackChooser build(Context context) {
|
||||
if (sIconPackBuilder == null) { //if there is no instance available... create new one
|
||||
synchronized (IconPackChooser.class) {
|
||||
if (sIconPackBuilder == null) {
|
||||
sIconPackBuilder = new IconPackChooser();
|
||||
|
||||
for (String iconPackString : BuildConfig.ICON_PACKS) {
|
||||
addOrCatchNewIconPack(context, iconPackString);
|
||||
}
|
||||
if (iconPackList.isEmpty()) {
|
||||
Log.e(TAG, "Icon packs can't be load, retry with one by default");
|
||||
addDefaultIconPack(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sIconPackBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct dynamically the icon pack provide by the default string resource "resource_id"
|
||||
*/
|
||||
private static void addDefaultIconPack(Context context) {
|
||||
int resourceId = context.getResources().getIdentifier("resource_id", "string", context.getPackageName());
|
||||
iconPackList.add(new IconPack(context, resourceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to add new icon pack or catch exception if not retrieve
|
||||
*/
|
||||
private static void addOrCatchNewIconPack(Context context, String iconPackString) {
|
||||
try {
|
||||
iconPackList.add(new IconPack(context, context.getResources().getIdentifier(
|
||||
iconPackString + "_resource_id",
|
||||
"string",
|
||||
context.getPackageName())));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Icon pack "+ iconPackString +" can't be load");
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSelectedIconPack(String iconPackIdString) {
|
||||
for(IconPack iconPack : iconPackList) {
|
||||
if (iconPack.getId().equals(iconPackIdString)) {
|
||||
App.getDB().getDrawFactory().clearCache();
|
||||
iconPackSelected = iconPack;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current IconPack used
|
||||
*
|
||||
* @param context Context to build the icon pack if not already build
|
||||
* @return IconPack currently in usage
|
||||
*/
|
||||
public static IconPack getSelectedIconPack(Context context) {
|
||||
build(context);
|
||||
if (iconPackSelected == null)
|
||||
setSelectedIconPack(PreferencesUtil.getIconPackSelectedId(context));
|
||||
return iconPackSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of IconPack available
|
||||
*
|
||||
* @param context Context to build the icon pack if not already build
|
||||
* @return IconPack available
|
||||
*/
|
||||
public static List<IconPack> getIconPackList(Context context) {
|
||||
build(context);
|
||||
return iconPackList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.kunzisoft.keepass.icons;
|
||||
|
||||
public class IconPackUnknownException extends Exception{
|
||||
|
||||
IconPackUnknownException() {
|
||||
super("Icon pack isn't defined");
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.icons;
|
||||
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
public class Icons {
|
||||
private static SparseIntArray icons = null;
|
||||
|
||||
private static void buildList() {
|
||||
if (icons == null) {
|
||||
icons = new SparseIntArray();
|
||||
|
||||
Class<com.kunzisoft.keepass.R.drawable> c = com.kunzisoft.keepass.R.drawable.class;
|
||||
|
||||
Field[] fields = c.getFields();
|
||||
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
String fieldName = fields[i].getName();
|
||||
if (fieldName.matches("ic\\d{2}.*")) {
|
||||
String sNum = fieldName.substring(2, 4);
|
||||
int num = Integer.parseInt(sNum);
|
||||
if (num > 69) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int resId;
|
||||
try {
|
||||
resId = fields[i].getInt(null);
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
icons.put(num, resId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int iconToResId(int iconId) {
|
||||
buildList();
|
||||
|
||||
return icons.get(iconId, R.drawable.ic99_blank);
|
||||
}
|
||||
|
||||
public static int count() {
|
||||
buildList();
|
||||
|
||||
return icons.size();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@
|
||||
package com.kunzisoft.keepass.password;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
|
||||
@@ -35,27 +34,25 @@ public class AssignPasswordHelper {
|
||||
|
||||
private Context context;
|
||||
|
||||
private String masterPassword;
|
||||
private Uri keyfile;
|
||||
private String masterPassword = null;
|
||||
private Uri keyfile = null;
|
||||
|
||||
public AssignPasswordHelper(Context context,
|
||||
boolean withMasterPassword,
|
||||
String masterPassword,
|
||||
boolean withKeyFile,
|
||||
Uri keyfile) {
|
||||
this.context = context;
|
||||
this.masterPassword = masterPassword;
|
||||
this.keyfile = keyfile;
|
||||
if (withMasterPassword)
|
||||
this.masterPassword = masterPassword;
|
||||
if (withKeyFile)
|
||||
this.keyfile = keyfile;
|
||||
}
|
||||
|
||||
public void assignPasswordInDatabase(FileOnFinish fileOnFinish) {
|
||||
SetPassword sp = new SetPassword(context, App.getDB(), masterPassword, keyfile, new AfterSave(fileOnFinish, new Handler()));
|
||||
final ProgressTask pt = new ProgressTask(context, sp, R.string.saving_database);
|
||||
boolean valid = sp.validatePassword(context, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
pt.run();
|
||||
}
|
||||
});
|
||||
boolean valid = sp.validatePassword(context, (dialog, which) -> pt.run());
|
||||
|
||||
if (valid) {
|
||||
pt.run();
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
@@ -45,9 +46,12 @@ import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.activities.GroupActivity;
|
||||
import com.kunzisoft.keepass.activities.LockingActivity;
|
||||
@@ -104,9 +108,11 @@ public class PasswordActivity extends StylishActivity
|
||||
private static final String PREF_KEY_VALUE_PREFIX = "valueFor_"; // key is a combination of db file name and this prefix
|
||||
private static final String PREF_KEY_IV_PREFIX = "ivFor_"; // key is a combination of db file name and this prefix
|
||||
|
||||
private Toolbar toolbar;
|
||||
private View fingerprintContainerView;
|
||||
private FingerPrintAnimatedVector fingerPrintAnimatedVector;
|
||||
private TextView fingerprintTextView;
|
||||
private ImageView fingerprintImageView;
|
||||
private TextView filenameView;
|
||||
private EditText passwordView;
|
||||
private EditText keyFileView;
|
||||
@@ -229,7 +235,7 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
setContentView(R.layout.password);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
@@ -281,15 +287,18 @@ public class PasswordActivity extends StylishActivity
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerprintContainerView = findViewById(R.id.fingerprint_container);
|
||||
fingerprintTextView = findViewById(R.id.fingerprint_label);
|
||||
fingerprintImageView = findViewById(R.id.fingerprint_image);
|
||||
initForFingerprint();
|
||||
fingerPrintAnimatedVector = new FingerPrintAnimatedVector(this,
|
||||
findViewById(R.id.fingerprint_image));
|
||||
fingerprintImageView);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
autofillHelper.retrieveAssistStructure(getIntent());
|
||||
}
|
||||
|
||||
checkAndPerformedEducation();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -315,6 +324,63 @@ public class PasswordActivity extends StylishActivity
|
||||
.execute(getIntent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and display learning views
|
||||
* Displays the explanation for a database opening with fingerprints if available
|
||||
*/
|
||||
private void checkAndPerformedEducation() {
|
||||
if (!PreferencesUtil.isEducationUnlockPerformed(this)) {
|
||||
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forView(findViewById(R.id.password_input_container),
|
||||
getString(R.string.education_unlock_title),
|
||||
getString(R.string.education_unlock_summary))
|
||||
.dimColor(R.color.green)
|
||||
.icon(ContextCompat.getDrawable(this, R.mipmap.ic_launcher_round))
|
||||
.tintTarget(false)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetClick(TapTargetView view) {
|
||||
super.onTargetClick(view);
|
||||
checkAndPerformedEducationForFingerprint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
checkAndPerformedEducationForFingerprint();
|
||||
|
||||
}
|
||||
});
|
||||
// TODO make a period for donation
|
||||
PreferencesUtil.saveEducationPreference(PasswordActivity.this, R.string.education_unlock_key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and display learning views
|
||||
* Displays fingerprints if available
|
||||
*/
|
||||
private void checkAndPerformedEducationForFingerprint() {
|
||||
if (PreferencesUtil.isFingerprintEnable(getApplicationContext())) {
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forView(fingerprintImageView,
|
||||
getString(R.string.education_fingerprint_title),
|
||||
getString(R.string.education_fingerprint_summary))
|
||||
.tintTarget(false)
|
||||
.cancelable(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onOuterCircleClick(TapTargetView view) {
|
||||
super.onOuterCircleClick(view);
|
||||
view.dismiss(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostInitTask(Uri dbUri, Uri keyFileUri, Integer errorStringId) {
|
||||
mDbUri = dbUri;
|
||||
@@ -722,7 +788,7 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
private void verifyCheckboxesAndLoadDatabase(String pass, Uri keyfile) {
|
||||
if (!checkboxPasswordView.isChecked()) {
|
||||
pass = "";
|
||||
pass = null;
|
||||
}
|
||||
if (!checkboxKeyfileView.isChecked()) {
|
||||
keyfile = null;
|
||||
@@ -757,12 +823,14 @@ public class PasswordActivity extends StylishActivity
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
MenuUtil.defaultMenuInflater(inflater, menu);
|
||||
if (!fingerprintMustBeConfigured
|
||||
&& prefsNoBackup.contains(getPreferenceKeyValue()) )
|
||||
inflater.inflate(R.menu.fingerprint, menu);
|
||||
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ public class MainPreferenceFragment extends PreferenceFragmentCompat implements
|
||||
preference = findPreference(getString(R.string.settings_form_filling_key));
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
|
||||
preference = findPreference(getString(R.string.settings_appearance_key));
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
|
||||
preference = findPreference(getString(R.string.db_key));
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
Database db = App.getDB();
|
||||
@@ -84,21 +87,25 @@ public class MainPreferenceFragment extends PreferenceFragmentCompat implements
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
// here you should use the same keys as you used in the xml-file
|
||||
if (preference.getKey().equals(getString(R.string.app_key))) {
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.NESTED_SCREEN_APP_KEY);
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.Screen.APPLICATION);
|
||||
}
|
||||
|
||||
if (preference.getKey().equals(getString(R.string.settings_form_filling_key))) {
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.NESTED_SCREEN_FORM_FILLING_KEY);
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.Screen.FORM_FILLING);
|
||||
}
|
||||
|
||||
if (preference.getKey().equals(getString(R.string.db_key))) {
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.NESTED_SCREEN_DB_KEY);
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.Screen.DATABASE);
|
||||
}
|
||||
|
||||
if (preference.getKey().equals(getString(R.string.settings_appearance_key))) {
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.Screen.APPEARANCE);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onNestedPreferenceSelected(int key);
|
||||
void onNestedPreferenceSelected(NestedSettingsFragment.Screen key);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.settings;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
@@ -40,12 +41,16 @@ import android.util.Log;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.BuildConfig;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
import com.kunzisoft.keepass.database.Database;
|
||||
import com.kunzisoft.keepass.dialogs.ProFeatureDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.StorageAccessFrameworkDialog;
|
||||
import com.kunzisoft.keepass.dialogs.UnavailableFeatureDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.UnderDevelopmentFeatureDialogFragment;
|
||||
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseDescriptionPreferenceDialogFragmentCompat;
|
||||
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseNamePreferenceDialogFragmentCompat;
|
||||
import com.kunzisoft.keepass.settings.preferenceDialogFragment.RoundsPreferenceDialogFragmentCompat;
|
||||
@@ -54,19 +59,21 @@ import com.kunzisoft.keepass.stylish.Stylish;
|
||||
public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
|
||||
public static final int NESTED_SCREEN_APP_KEY = 1;
|
||||
public static final int NESTED_SCREEN_FORM_FILLING_KEY = 2;
|
||||
public static final int NESTED_SCREEN_DB_KEY = 3;
|
||||
public enum Screen {
|
||||
APPLICATION, FORM_FILLING, DATABASE, APPEARANCE
|
||||
}
|
||||
|
||||
private static final String TAG_KEY = "NESTED_KEY";
|
||||
|
||||
private static final int REQUEST_CODE_AUTOFILL = 5201;
|
||||
|
||||
public static NestedSettingsFragment newInstance(int key) {
|
||||
private int count = 0;
|
||||
|
||||
public static NestedSettingsFragment newInstance(Screen key) {
|
||||
NestedSettingsFragment fragment = new NestedSettingsFragment();
|
||||
// supply arguments to bundle.
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(TAG_KEY, key);
|
||||
args.putInt(TAG_KEY, key.ordinal());
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
@@ -90,11 +97,14 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
int key = getArguments().getInt(TAG_KEY);
|
||||
int key = 0;
|
||||
if (getArguments() != null)
|
||||
key = getArguments().getInt(TAG_KEY);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
switch (key) {
|
||||
case NESTED_SCREEN_APP_KEY:
|
||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||
switch (Screen.values()[key]) {
|
||||
case APPLICATION:
|
||||
setPreferencesFromResource(R.xml.application_preferences, rootKey);
|
||||
|
||||
Preference keyFile = findPreference(getString(R.string.keyfile_key));
|
||||
keyFile.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
@@ -136,14 +146,6 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference stylePreference = findPreference(getString(R.string.setting_style_key));
|
||||
stylePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String styleString = (String) newValue;
|
||||
Stylish.assignStyle(getActivity(), styleString);
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
});
|
||||
|
||||
SwitchPreference fingerprintEnablePreference =
|
||||
(SwitchPreference) findPreference(getString(R.string.fingerprint_enable_key));
|
||||
// < M solve verifyError exception
|
||||
@@ -203,9 +205,10 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NESTED_SCREEN_FORM_FILLING_KEY:
|
||||
case FORM_FILLING:
|
||||
setPreferencesFromResource(R.xml.form_filling_preferences, rootKey);
|
||||
|
||||
SwitchPreference autoFillEnablePreference =
|
||||
@@ -265,10 +268,14 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
SwitchPreference keyboardPreference = (SwitchPreference) findPreference(getString(R.string.magic_keyboard_key));
|
||||
preferenceInDevelopment(keyboardPreference);
|
||||
|
||||
break;
|
||||
|
||||
case NESTED_SCREEN_DB_KEY:
|
||||
setPreferencesFromResource(R.xml.db_preferences, rootKey);
|
||||
case DATABASE:
|
||||
setPreferencesFromResource(R.xml.database_preferences, rootKey);
|
||||
|
||||
Database db = App.getDB();
|
||||
if (db.getLoaded()) {
|
||||
@@ -310,10 +317,12 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
// Encryption Algorithm
|
||||
Preference algorithmPref = findPreference(getString(R.string.encryption_algorithm_key));
|
||||
algorithmPref.setSummary(db.getEncryptionAlgorithmName(getResources()));
|
||||
preferenceInDevelopment(algorithmPref);
|
||||
|
||||
// Key derivation function
|
||||
Preference kdfPref = findPreference(getString(R.string.key_derivation_function_key));
|
||||
kdfPref.setSummary(db.getKeyDerivationName());
|
||||
preferenceInDevelopment(kdfPref);
|
||||
|
||||
// Round encryption
|
||||
Preference roundPref = findPreference(getString(R.string.transform_rounds_key));
|
||||
@@ -325,11 +334,90 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
|
||||
break;
|
||||
|
||||
case APPEARANCE:
|
||||
setPreferencesFromResource(R.xml.appearance_preferences, rootKey);
|
||||
|
||||
Preference stylePreference = findPreference(getString(R.string.setting_style_key));
|
||||
stylePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String styleIdString = (String) newValue;
|
||||
if (!(!BuildConfig.CLOSED_STORE && PreferencesUtil.isEducationScreenReclickedPerformed(getContext())))
|
||||
for (String themeIdDisabled : BuildConfig.STYLES_DISABLED) {
|
||||
if (themeIdDisabled.equals(styleIdString)) {
|
||||
ProFeatureDialogFragment dialogFragment = new ProFeatureDialogFragment();
|
||||
if (getFragmentManager() != null)
|
||||
dialogFragment.show(getFragmentManager(), "pro_feature_dialog");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Stylish.assignStyle(styleIdString);
|
||||
if (getActivity() != null)
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference iconPackPreference = findPreference(getString(R.string.setting_icon_pack_choose_key));
|
||||
iconPackPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String iconPackId = (String) newValue;
|
||||
if (!(!BuildConfig.CLOSED_STORE && PreferencesUtil.isEducationScreenReclickedPerformed(getContext())))
|
||||
for (String iconPackIdDisabled : BuildConfig.ICON_PACKS_DISABLED) {
|
||||
if (iconPackIdDisabled.equals(iconPackId)) {
|
||||
ProFeatureDialogFragment dialogFragment = new ProFeatureDialogFragment();
|
||||
if (getFragmentManager() != null)
|
||||
dialogFragment.show(getFragmentManager(), "pro_feature_dialog");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IconPackChooser.setSelectedIconPack(iconPackId);
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference resetEducationScreens = findPreference(getString(R.string.reset_education_screens_key));
|
||||
resetEducationScreens.setOnPreferenceClickListener(preference -> {
|
||||
// To allow only one toast
|
||||
if (count == 0) {
|
||||
SharedPreferences sharedPreferences = PreferencesUtil.getEducationSharedPreferences(getContext());
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
for (int resourceId : PreferencesUtil.educationResourceKeys) {
|
||||
editor.putBoolean(getString(resourceId), false);
|
||||
}
|
||||
editor.apply();
|
||||
Toast.makeText(getContext(), R.string.reset_education_screens_text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
count++;
|
||||
return false;
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void preferenceInDevelopment(Preference preferenceInDev) {
|
||||
preferenceInDev.setOnPreferenceClickListener(preference -> {
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
assert fragmentManager != null;
|
||||
try { // don't check if we can
|
||||
((SwitchPreference) preference).setChecked(false);
|
||||
} catch (Exception e) {}
|
||||
new UnderDevelopmentFeatureDialogFragment().show(getFragmentManager(), "underDevFeatureDialog");
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if(count==10) {
|
||||
if (getActivity()!=null)
|
||||
PreferencesUtil.getEducationSharedPreferences(getActivity()).edit()
|
||||
.putBoolean(getString(R.string.education_screen_reclicked_key), true).apply();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference) {
|
||||
|
||||
@@ -358,14 +446,16 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
}
|
||||
}
|
||||
|
||||
public static String retrieveTitle(Resources resources, int key) {
|
||||
public static String retrieveTitle(Resources resources, Screen key) {
|
||||
switch (key) {
|
||||
case NESTED_SCREEN_APP_KEY:
|
||||
case APPLICATION:
|
||||
return resources.getString(R.string.menu_app_settings);
|
||||
case NESTED_SCREEN_FORM_FILLING_KEY:
|
||||
case FORM_FILLING:
|
||||
return resources.getString(R.string.menu_form_filling_settings);
|
||||
case NESTED_SCREEN_DB_KEY:
|
||||
case DATABASE:
|
||||
return resources.getString(R.string.menu_db_settings);
|
||||
case APPEARANCE:
|
||||
return resources.getString(R.string.appearance);
|
||||
default:
|
||||
return resources.getString(R.string.settings);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.Set;
|
||||
public class PreferencesUtil {
|
||||
|
||||
private static final String NO_BACKUP_PREFERENCE_FILE_NAME = "nobackup";
|
||||
private static final String EDUCATION_PREFERENCE = "kdbxeducation";
|
||||
|
||||
public static SharedPreferences getNoBackupSharedPreferences(Context ctx) {
|
||||
return ctx.getSharedPreferences(
|
||||
@@ -40,6 +41,12 @@ public class PreferencesUtil {
|
||||
Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static SharedPreferences getEducationSharedPreferences(Context ctx) {
|
||||
return ctx.getSharedPreferences(
|
||||
PreferencesUtil.EDUCATION_PREFERENCE,
|
||||
Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static void deleteAllValuesFromNoBackupPreferences(Context ctx) {
|
||||
SharedPreferences prefsNoBackup = getNoBackupSharedPreferences(ctx);
|
||||
SharedPreferences.Editor sharedPreferencesEditor = prefsNoBackup.edit();
|
||||
@@ -137,4 +144,200 @@ public class PreferencesUtil {
|
||||
return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_key),
|
||||
ctx.getResources().getBoolean(R.bool.allow_copy_password_default));
|
||||
}
|
||||
|
||||
public static String getIconPackSelectedId(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return prefs.getString(
|
||||
context.getString(R.string.setting_icon_pack_choose_key),
|
||||
context.getString(R.string.setting_icon_pack_choose_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* All preference keys associated with education
|
||||
*/
|
||||
public static int[] educationResourceKeys = new int[] {
|
||||
R.string.education_create_db_key,
|
||||
R.string.education_select_db_key,
|
||||
R.string.education_open_link_db_key,
|
||||
R.string.education_unlock_key,
|
||||
R.string.education_search_key,
|
||||
R.string.education_new_node_key,
|
||||
R.string.education_sort_key,
|
||||
R.string.education_lock_key,
|
||||
R.string.education_copy_username_key,
|
||||
R.string.education_entry_edit_key,
|
||||
R.string.education_password_generator_key,
|
||||
R.string.education_entry_new_field_key
|
||||
};
|
||||
|
||||
/**
|
||||
* Register education preferences as true in EDUCATION_PREFERENCE SharedPreferences
|
||||
*
|
||||
* @param context The context to retrieve the key string in XML
|
||||
* @param educationKeys Keys to save as boolean 'true'
|
||||
*/
|
||||
public static void saveEducationPreference(Context context, int... educationKeys) {
|
||||
SharedPreferences sharedPreferences = PreferencesUtil.getEducationSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
for (int key : educationKeys) {
|
||||
editor.putBoolean(context.getString(key), true);
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the database creation has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_create_db_key key
|
||||
*/
|
||||
public static boolean isEducationCreateDatabasePerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_create_db_key),
|
||||
context.getResources().getBoolean(R.bool.education_create_db_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the database selection has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_select_db_key key
|
||||
*/
|
||||
public static boolean isEducationSelectDatabasePerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_select_db_key),
|
||||
context.getResources().getBoolean(R.bool.education_select_db_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the database selection has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_select_db_key key
|
||||
*/
|
||||
public static boolean isEducationOpenLinkDatabasePerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_open_link_db_key),
|
||||
context.getResources().getBoolean(R.bool.education_open_link_db_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the database unlock has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_unlock_key key
|
||||
*/
|
||||
public static boolean isEducationUnlockPerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_unlock_key),
|
||||
context.getResources().getBoolean(R.bool.education_unlock_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of search has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_search_key key
|
||||
*/
|
||||
public static boolean isEducationSearchPerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_search_key),
|
||||
context.getResources().getBoolean(R.bool.education_search_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of add new node has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_new_node_key key
|
||||
*/
|
||||
public static boolean isEducationNewNodePerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_new_node_key),
|
||||
context.getResources().getBoolean(R.bool.education_new_node_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the sort has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_sort_key key
|
||||
*/
|
||||
public static boolean isEducationSortPerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_sort_key),
|
||||
context.getResources().getBoolean(R.bool.education_sort_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the database lock has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_lock_key key
|
||||
*/
|
||||
public static boolean isEducationLockPerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_lock_key),
|
||||
context.getResources().getBoolean(R.bool.education_lock_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the username copy has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_copy_username_key key
|
||||
*/
|
||||
public static boolean isEducationCopyUsernamePerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_copy_username_key),
|
||||
context.getResources().getBoolean(R.bool.education_copy_username_key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the entry edition has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_entry_edit_key key
|
||||
*/
|
||||
public static boolean isEducationEntryEditPerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_entry_edit_key),
|
||||
context.getResources().getBoolean(R.bool.education_entry_edit_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the password generator has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_password_generator_key key
|
||||
*/
|
||||
public static boolean isEducationPasswordGeneratorPerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_password_generator_key),
|
||||
context.getResources().getBoolean(R.bool.education_password_generator_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the explanatory view of the new fields button in an entry has already been displayed.
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_entry_new_field_key key
|
||||
*/
|
||||
public static boolean isEducationEntryNewFieldPerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_entry_new_field_key),
|
||||
context.getResources().getBoolean(R.bool.education_entry_new_field_default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if the reset education preference has been reclicked
|
||||
*
|
||||
* @param context The context to open the SharedPreferences
|
||||
* @return boolean value of education_screen_reclicked_key key
|
||||
*/
|
||||
public static boolean isEducationScreenReclickedPerformed(Context context) {
|
||||
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||
return prefs.getBoolean(context.getString(R.string.education_screen_reclicked_key),
|
||||
context.getResources().getBoolean(R.bool.education_screen_reclicked_default));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,17 +101,19 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// this if statement is necessary to navigate through nested and main fragments
|
||||
if (getFragmentManager().getBackStackEntryCount() == 0) {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
super.onBackPressed();
|
||||
} else {
|
||||
getFragmentManager().popBackStack();
|
||||
getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
toolbar.setTitle(R.string.settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedPreferenceSelected(int key) {
|
||||
public void onNestedPreferenceSelected(NestedSettingsFragment.Screen key) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||
R.anim.slide_in_left, R.anim.slide_out_right)
|
||||
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key), TAG_NESTED)
|
||||
.addToBackStack(TAG_NESTED)
|
||||
.commit();
|
||||
|
||||
@@ -25,6 +25,6 @@ public class SettingsAutofillActivity extends SettingsActivity {
|
||||
|
||||
@Override
|
||||
protected Fragment retrieveMainFragment() {
|
||||
return NestedSettingsFragment.newInstance(NestedSettingsFragment.NESTED_SCREEN_FORM_FILLING_KEY);
|
||||
return NestedSettingsFragment.newInstance(NestedSettingsFragment.Screen.FORM_FILLING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.kunzisoft.keepass.settings.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.icons.IconPack;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IconPackListPreference extends ListPreference {
|
||||
|
||||
public IconPackListPreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public IconPackListPreference(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.dialogPreferenceStyle);
|
||||
}
|
||||
|
||||
public IconPackListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, defStyleAttr);
|
||||
}
|
||||
|
||||
public IconPackListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
List<String> entries = new ArrayList<>();
|
||||
List<String> values = new ArrayList<>();
|
||||
for (IconPack iconPack : IconPackChooser.getIconPackList(context)) {
|
||||
entries.add(iconPack.getName());
|
||||
values.add(iconPack.getId());
|
||||
}
|
||||
|
||||
setEntries(entries.toArray(new String[0]));
|
||||
setEntryValues(values.toArray(new String[0]));
|
||||
setDefaultValue(IconPackChooser.getSelectedIconPack(context).getId());
|
||||
}
|
||||
}
|
||||
@@ -26,26 +26,46 @@ import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
/**
|
||||
* Class that provides functions to retrieve and assign a theme to a module
|
||||
*/
|
||||
public class Stylish {
|
||||
|
||||
protected static String stylishPrefKey;
|
||||
|
||||
protected static String themeString;
|
||||
|
||||
/**
|
||||
* Initialize the class with a theme preference
|
||||
* @param context Context to retrieve the theme preference
|
||||
*/
|
||||
public static void init(Context context) {
|
||||
stylishPrefKey = context.getString(R.string.setting_style_key);
|
||||
String stylishPrefKey = context.getString(R.string.setting_style_key);
|
||||
Log.d(Stylish.class.getName(), "Attatching to " + context.getPackageName());
|
||||
themeString = PreferenceManager.getDefaultSharedPreferences(context).getString(stylishPrefKey, context.getString(R.string.list_style_name_light));
|
||||
}
|
||||
|
||||
public static void assignStyle(Context context, String styleString) {
|
||||
/**
|
||||
* Assign the style to the class attribute
|
||||
* @param styleString Style id String
|
||||
*/
|
||||
public static void assignStyle(String styleString) {
|
||||
themeString = styleString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that returns the current id of the style selected in the preference
|
||||
* @param context Context to retrieve the id
|
||||
* @return Id of the style
|
||||
*/
|
||||
public static @StyleRes int getThemeId(Context context) {
|
||||
|
||||
if (themeString.equals(context.getString(R.string.list_style_name_night)))
|
||||
return R.style.KeepassDXStyle_Night;
|
||||
else if (themeString.equals(context.getString(R.string.list_style_name_dark)))
|
||||
return R.style.KeepassDXStyle_Dark;
|
||||
else if (themeString.equals(context.getString(R.string.list_style_name_blue)))
|
||||
return R.style.KeepassDXStyle_Blue;
|
||||
else if (themeString.equals(context.getString(R.string.list_style_name_purple)))
|
||||
return R.style.KeepassDXStyle_Purple;
|
||||
|
||||
return R.style.KeepassDXStyle_Light;
|
||||
}
|
||||
|
||||
@@ -21,25 +21,41 @@ package com.kunzisoft.keepass.utils;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.kunzisoft.keepass.BuildConfig;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.activities.AboutActivity;
|
||||
import com.kunzisoft.keepass.settings.SettingsActivity;
|
||||
import com.kunzisoft.keepass.stylish.StylishActivity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class MenuUtil {
|
||||
|
||||
public static void donationMenuInflater(MenuInflater inflater, Menu menu) {
|
||||
if(!(BuildConfig.FULL_VERSION && BuildConfig.GOOGLE_PLAY_VERSION))
|
||||
if(!(BuildConfig.FULL_VERSION && BuildConfig.CLOSED_STORE))
|
||||
inflater.inflate(R.menu.donation, menu);
|
||||
}
|
||||
|
||||
public static void addDonationTapTargetIfAllowed(List<TapTarget> tapTargets,
|
||||
Toolbar toolbar,
|
||||
String title,
|
||||
String summary) {
|
||||
if (!(BuildConfig.FULL_VERSION && BuildConfig.CLOSED_STORE)) {
|
||||
tapTargets.add(TapTarget.forToolbarMenuItem(toolbar,
|
||||
R.id.menu_donate,
|
||||
title,
|
||||
summary));
|
||||
}
|
||||
}
|
||||
|
||||
public static void defaultMenuInflater(MenuInflater inflater, Menu menu) {
|
||||
donationMenuInflater(inflater, menu);
|
||||
inflater.inflate(R.menu.default_menu, menu);
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.utils;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SpannableReplacer {
|
||||
private final CharSequence mSource;
|
||||
private final CharSequence mReplacement;
|
||||
private final Matcher mMatcher;
|
||||
private int mAppendPosition;
|
||||
private final boolean mIsSpannable;
|
||||
|
||||
public static CharSequence replace(CharSequence source, String regex,
|
||||
CharSequence replacement) {
|
||||
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(source);
|
||||
return new SpannableReplacer(source, matcher, replacement).doReplace();
|
||||
}
|
||||
|
||||
private SpannableReplacer(CharSequence source, Matcher matcher,
|
||||
CharSequence replacement) {
|
||||
mSource = source;
|
||||
mReplacement = replacement;
|
||||
mMatcher = matcher;
|
||||
mAppendPosition = 0;
|
||||
mIsSpannable = replacement instanceof Spannable;
|
||||
}
|
||||
|
||||
private CharSequence doReplace() {
|
||||
SpannableStringBuilder buffer = new SpannableStringBuilder();
|
||||
while (mMatcher.find()) {
|
||||
appendReplacement(buffer);
|
||||
}
|
||||
return appendTail(buffer);
|
||||
}
|
||||
|
||||
private void appendReplacement(SpannableStringBuilder buffer) {
|
||||
buffer.append(mSource.subSequence(mAppendPosition, mMatcher.start()));
|
||||
CharSequence replacement = mIsSpannable
|
||||
? copyCharSequenceWithSpans(mReplacement)
|
||||
: mReplacement;
|
||||
buffer.append(replacement);
|
||||
|
||||
mAppendPosition = mMatcher.end();
|
||||
}
|
||||
|
||||
public SpannableStringBuilder appendTail(SpannableStringBuilder buffer) {
|
||||
buffer.append(mSource.subSequence(mAppendPosition, mSource.length()));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// This is a weird way of copying spans, but I don't know any better way.
|
||||
private CharSequence copyCharSequenceWithSpans(CharSequence string) {
|
||||
Parcel parcel = Parcel.obtain();
|
||||
try {
|
||||
TextUtils.writeToParcel(string, parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -61,66 +56,18 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
private final static String stringToStrikeThrough = "0";
|
||||
|
||||
/**
|
||||
* Replace font by monospace and strike through all zeros, must be called after seText()
|
||||
* Replace font by monospace, must be called after seText()
|
||||
*/
|
||||
public static void applyFontVisibilityTo(final TextView textView) {
|
||||
textView.setText(strikeThroughToZero(textView.getText()));
|
||||
textView.setTypeface(Typeface.MONOSPACE);
|
||||
public static void applyFontVisibilityTo(final Context context, final TextView textView) {
|
||||
Typeface typeFace=Typeface.createFromAsset(context.getAssets(),"fonts/DroidSansMonoSlashed.ttf");
|
||||
textView.setTypeface(typeFace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace font by monospace and strike through all zeros, must be called after seText()
|
||||
* Replace font by monospace, must be called after seText()
|
||||
*/
|
||||
public static void applyFontVisibilityTo(final EditText editText) {
|
||||
// Assign spans to default text
|
||||
editText.setText(strikeThroughToZero(editText.getText()));
|
||||
// Add spans for each new 0 character
|
||||
class TextWatcherCustomFont implements TextWatcher {
|
||||
|
||||
private boolean applySpannable;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
|
||||
applySpannable = count < after;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||
if (applySpannable) {
|
||||
String text = charSequence.toString();
|
||||
if (text.contains(stringToStrikeThrough)) {
|
||||
for (int index = text.indexOf(stringToStrikeThrough);
|
||||
index >= 0; index = text.indexOf(stringToStrikeThrough,
|
||||
index + 1)) {
|
||||
editText.getText().setSpan(new StrikethroughSpan(),
|
||||
index,
|
||||
index + stringToStrikeThrough.length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {}
|
||||
};
|
||||
TextWatcher textWatcher = new TextWatcherCustomFont();
|
||||
editText.addTextChangedListener(textWatcher);
|
||||
editText.setTypeface(Typeface.MONOSPACE);
|
||||
public static void applyFontVisibilityTo(final Context context, final EditText editText) {
|
||||
applyFontVisibilityTo(context, (TextView) editText);
|
||||
}
|
||||
|
||||
private static CharSequence strikeThroughToZero(final CharSequence text) {
|
||||
if (text.toString().contains(stringToStrikeThrough)) {
|
||||
SpannableString spannable = new SpannableString(stringToStrikeThrough);
|
||||
spannable.setSpan(new StrikethroughSpan(),
|
||||
0,
|
||||
stringToStrikeThrough.length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return SpannableReplacer.replace(text, stringToStrikeThrough, spannable);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ public class AddNodeButtonView extends RelativeLayout {
|
||||
addGroupEnable = true;
|
||||
|
||||
addButtonView = findViewById(R.id.add_button);
|
||||
addEntryView = findViewById(R.id.add_entry);
|
||||
addGroupView = findViewById(R.id.add_group);
|
||||
addEntryView = findViewById(R.id.container_add_entry);
|
||||
addGroupView = findViewById(R.id.container_add_group);
|
||||
|
||||
animationDuration = 300L;
|
||||
|
||||
@@ -148,6 +148,15 @@ public class AddNodeButtonView extends RelativeLayout {
|
||||
addButtonView.hide(onAddButtonVisibilityChangedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the animation to close the button
|
||||
*/
|
||||
public void openButtonIfClose() {
|
||||
if(state.equals(State.CLOSE)) {
|
||||
startGlobalAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the animation to close the button
|
||||
*/
|
||||
@@ -186,11 +195,16 @@ public class AddNodeButtonView extends RelativeLayout {
|
||||
}
|
||||
|
||||
public void setAddEntryClickListener(OnClickListener onClickListener) {
|
||||
if (addEntryEnable)
|
||||
if (addEntryEnable) {
|
||||
addEntryView.setOnClickListener(view -> {
|
||||
onClickListener.onClick(view);
|
||||
closeButtonIfOpen();
|
||||
});
|
||||
addEntryView.setOnClickListener(view -> {
|
||||
onClickListener.onClick(view);
|
||||
closeButtonIfOpen();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void startGlobalAnimation() {
|
||||
|
||||
@@ -115,7 +115,7 @@ public class EntryContentsView extends LinearLayout {
|
||||
userNameContainerView.setVisibility(VISIBLE);
|
||||
userNameView.setText(userName);
|
||||
if (fontInVisibility)
|
||||
Util.applyFontVisibilityTo(userNameView);
|
||||
Util.applyFontVisibilityTo(getContext(), userNameView);
|
||||
} else {
|
||||
userNameContainerView.setVisibility(GONE);
|
||||
}
|
||||
@@ -125,12 +125,16 @@ public class EntryContentsView extends LinearLayout {
|
||||
userNameActionView.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
public boolean isUserNamePresent() {
|
||||
return userNameContainerView.getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
public void assignPassword(String password) {
|
||||
if (password != null && !password.isEmpty()) {
|
||||
passwordContainerView.setVisibility(VISIBLE);
|
||||
passwordView.setText(password);
|
||||
if (fontInVisibility)
|
||||
Util.applyFontVisibilityTo(passwordView);
|
||||
Util.applyFontVisibilityTo(getContext(), passwordView);
|
||||
passwordActionView.setVisibility(GONE);
|
||||
} else {
|
||||
passwordContainerView.setVisibility(GONE);
|
||||
@@ -183,7 +187,7 @@ public class EntryContentsView extends LinearLayout {
|
||||
commentContainerView.setVisibility(VISIBLE);
|
||||
commentView.setText(comment);
|
||||
if (fontInVisibility)
|
||||
Util.applyFontVisibilityTo(commentView);
|
||||
Util.applyFontVisibilityTo(getContext(), commentView);
|
||||
} else {
|
||||
commentContainerView.setVisibility(GONE);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public class EntryCustomField extends LinearLayout {
|
||||
|
||||
public void applyFontVisibility(boolean fontInVisibility) {
|
||||
if (fontInVisibility)
|
||||
Util.applyFontVisibilityTo(valueView);
|
||||
Util.applyFontVisibilityTo(getContext(), valueView);
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
|
||||
@@ -86,7 +86,7 @@ public class EntryEditCustomField extends RelativeLayout {
|
||||
|
||||
public void setFontVisibility(boolean applyFontVisibility) {
|
||||
if (applyFontVisibility)
|
||||
Util.applyFontVisibilityTo(valueView);
|
||||
Util.applyFontVisibilityTo(getContext(), valueView);
|
||||
}
|
||||
|
||||
public void deleteViewFromParent() {
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX 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.
|
||||
*
|
||||
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class FileNameView extends RelativeLayout {
|
||||
|
||||
public FileNameView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FileNameView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
inflate(context);
|
||||
}
|
||||
|
||||
private void inflate(Context context) {
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.file_selection_filename, this);
|
||||
}
|
||||
|
||||
public void updateExternalStorageWarning() {
|
||||
int warning = -1;
|
||||
String state = Environment.getExternalStorageState();
|
||||
if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
|
||||
warning = R.string.warning_read_only;
|
||||
} else if (!state.equals(Environment.MEDIA_MOUNTED)) {
|
||||
warning = R.string.warning_unmounted;
|
||||
}
|
||||
|
||||
TextView tv = findViewById(R.id.label_warning);
|
||||
if (warning != -1) {
|
||||
tv.setText(warning);
|
||||
tv.setVisibility(VISIBLE);
|
||||
} else {
|
||||
tv.setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
app/src/main/res/anim/slide_in_left.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/* //device/apps/common/res/anim/slide_in_left.xml
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate android:fromXDelta="-50%p" android:toXDelta="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
26
app/src/main/res/anim/slide_in_right.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/* //device/apps/common/res/anim/slide_in_right.xml
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate android:fromXDelta="50%p" android:toXDelta="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
26
app/src/main/res/anim/slide_out_left.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/* //device/apps/common/res/anim/slide_out_left.xml
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate android:fromXDelta="0" android:toXDelta="-50%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
26
app/src/main/res/anim/slide_out_right.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/* //device/apps/common/res/anim/slide_out_right.xml
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate android:fromXDelta="0" android:toXDelta="50%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 189 B |
|
Before Width: | Height: | Size: 189 B |
@@ -6,12 +6,12 @@
|
||||
<item>
|
||||
<shape>
|
||||
<corners
|
||||
android:radius="2dp" />
|
||||
android:radius="0dp" />
|
||||
<padding
|
||||
android:left="0dp"
|
||||
android:right="0dp"
|
||||
android:top="8dp"
|
||||
android:bottom="8dp"/>
|
||||
android:top="12dp"
|
||||
android:bottom="12dp"/>
|
||||
<solid android:color="?attr/colorAccentCompat"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
18
app/src/main/res/drawable-v21/button_background_primary.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:color="@color/white"
|
||||
tools:targetApi="lollipop">
|
||||
<item>
|
||||
<shape>
|
||||
<corners
|
||||
android:radius="0dp" />
|
||||
<padding
|
||||
android:left="0dp"
|
||||
android:right="0dp"
|
||||
android:top="12dp"
|
||||
android:bottom="12dp"/>
|
||||
<solid android:color="?attr/colorPrimary"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:color="@color/white"
|
||||
tools:targetApi="lollipop">
|
||||
<item>
|
||||
<layer-list>
|
||||
<item android:bottom="2dp" android:left="2dp">
|
||||
<shape>
|
||||
<corners
|
||||
android:radius="2dp" />
|
||||
<padding
|
||||
android:left="14dp"
|
||||
android:right="14dp"
|
||||
android:top="4dp"
|
||||
android:bottom="6dp"/>
|
||||
<solid android:color="@color/orange"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
</item>
|
||||
</ripple>
|
||||