diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ + diff --git a/.github/images/IzzyOnDroid-badge.png b/.github/images/IzzyOnDroid-badge.png new file mode 100644 index 0000000..716fb4d Binary files /dev/null and b/.github/images/IzzyOnDroid-badge.png differ diff --git a/.github/images/Paypal-badge.png b/.github/images/Paypal-badge.png new file mode 100644 index 0000000..4a93e65 Binary files /dev/null and b/.github/images/Paypal-badge.png differ diff --git a/badges/appstore-badge.png b/.github/images/appstore-badge.png similarity index 100% rename from badges/appstore-badge.png rename to .github/images/appstore-badge.png diff --git a/.github/images/github-badge.png b/.github/images/github-badge.png new file mode 100644 index 0000000..c5eb5bf Binary files /dev/null and b/.github/images/github-badge.png differ diff --git a/badges/google-play-badge.png b/.github/images/google-play-badge.png similarity index 100% rename from badges/google-play-badge.png rename to .github/images/google-play-badge.png diff --git a/.github/images/icon_round.png b/.github/images/icon_round.png new file mode 100644 index 0000000..5a62e44 Binary files /dev/null and b/.github/images/icon_round.png differ diff --git a/.github/images/screenshot_0.png b/.github/images/screenshot_0.png new file mode 100644 index 0000000..65899af Binary files /dev/null and b/.github/images/screenshot_0.png differ diff --git a/.github/images/screenshot_1.png b/.github/images/screenshot_1.png new file mode 100644 index 0000000..121cadf Binary files /dev/null and b/.github/images/screenshot_1.png differ diff --git a/.github/images/screenshot_2.png b/.github/images/screenshot_2.png new file mode 100644 index 0000000..fc43482 Binary files /dev/null and b/.github/images/screenshot_2.png differ diff --git a/.github/images/screenshot_3.png b/.github/images/screenshot_3.png new file mode 100644 index 0000000..e2ac946 Binary files /dev/null and b/.github/images/screenshot_3.png differ diff --git a/.github/images/screenshot_4.png b/.github/images/screenshot_4.png new file mode 100644 index 0000000..9f3d74a Binary files /dev/null and b/.github/images/screenshot_4.png differ diff --git a/.github/images/screenshot_5.png b/.github/images/screenshot_5.png new file mode 100644 index 0000000..2cb1f67 Binary files /dev/null and b/.github/images/screenshot_5.png differ diff --git a/LICENSE b/LICENSE index d6aa7ae..bc9b52b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,673 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. - 1. Definitions. + Preamble - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. - "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. + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. - "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. + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. - "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). + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. - "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. + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. - "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." + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. - "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. + The precise terms and conditions for copying, distribution and +modification follow. - 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. + TERMS AND CONDITIONS - 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. + 0. Definitions. - 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: + "This License" refers to version 3 of the GNU General Public License. - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. - (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 + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. - (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. + A "covered work" means either the unmodified Program or a work based +on the Program. - 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. + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. - 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. + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. - 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. + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. - 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. + 1. Source Code. - 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. + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. - 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. + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. - END OF TERMS AND CONDITIONS + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. - APPENDIX: How to apply the Apache License to your work. + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. - 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. + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. - Copyright 2021 Fong Lam Hang + The Corresponding Source for a work in source code form is that +same work. - 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 + 2. Basic Permissions. - http://www.apache.org/licenses/LICENSE-2.0 + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. - 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. \ No newline at end of file + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Copyright (C) 2022 Tom Fong + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Simple QR Copyright (C) 2022 Tom Fong + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/README.md b/README.md index 9280eff..8097a40 100644 --- a/README.md +++ b/README.md @@ -1,203 +1,139 @@ # Simple QR +


-Simple QR -

- -

- About -• Features -• Support Developer -• Contribute + + Simple QR +

- Changelogs -• Framework -• Privacy Policy -• License + Simple and lightweight app to scan, create and store QR codes +

+
+

+ + + + +

## About -Simple QR (簡易QR) is an open-source app, providing the simplest way to scan, create and manage QR codes. No backend service connected. No data collected from you. No ads. +Simple QR is an open-source app to scan, create and store QR codes with a simple UI and experience. No backend service connected. No data collected. No ads. -Welcome to download the app from App Store or Google Play. +It's now available on the following platforms. -| Android | iOS | -|:-:|:-:| -| [](https://play.google.com/store/apps/details?id=com.tomfong.simpleqr) | [](https://apps.apple.com/us/app/simple-qr-by-tom-fong/id1621121553) | +| Google Play | GitHub | IzzyOnDroid | +|:-:|:-:|:-:| +| [](https://play.google.com/store/apps/details?id=com.tomfong.simpleqr) | [](https://github.com/tomfong/simple-qr/releases/latest) | [](https://apt.izzysoft.de/fdroid/index/apk/com.tomfong.simpleqr) | ## Features -1. Scan QR Code, 1D Barcode, Aztec Code, Data Matrix Code and PDF417 Barcode using camera. +By using the app, you can -2. Scan QR Code using image file. +1. Scan QR Code and other barcodes in a second, including UPC, EAN, Code 39/128, ITF, Codabar, Aztec, Data Matrix, PDF417, MaxiCode and GS1 DataBar. -3. Provide you post actions after scanning: -"Search" - use decoded content as keyword to execute web search -"Copy" - click to copy decoded content -"Base64" - execute Base64 encode and decode -"Share" - share the original QR code image -"Bookmark" - save the scanning record and bookmark it +2. Import image files and scan the QR Code on it. -4. Provide you special post actions, if the barcode content is URL, vCard Contact, Phone Number, Message or Email. +3. Create QR code from templates, which includes Free Text, URL, vCard Contact, Phone Number, Message, Email, Wi-Fi and Geolocation. -5. Create QR code from templates, including Free Text, URL, vCard Contact, Phone Number, Message, Email and Wi-Fi. +4. Automatically log results that you scan, create or view again. These logged records can be bookmarked for quick access, and also backupable. -6. View, manage and back up scanning records and bookmarks that stored in the local offline storage. +5. Do tasks on the result content with a tap, including but not limited to + * Use it as a keyword to do web search. + * Quickly copy it to the clipboard. + * Execute base64 encoding/decoding on it. + * Use it as a content to generate a new shareable QR code. + * Do corresponding tasks if it is a + * URL: Browse website / Open application + * vCard contact: Add contact + * Phone number: Phone call, add contact + * Message: Send message, add contact + * Email: Send email + * Geolocation: Open map -7. Enable torch/flashlight during scanning. +6. Customize the generated QR code, e.g. error correction level, color, margin and screen brightness. -8. Personalize the app and settings, e.g. language and color theme. +7. Customize the app, e.g. app initial page, language and color theme etc. -## Languages Supported +### Demo -- English (en) -- Traditional Chinese 正體中文 (zh-HK) -- Simplified Chinese 简体中文 (zh-CN) +[![Simple QR Demo](https://img.youtube.com/vi/TIC6ZAkWoXY/0.jpg)](https://www.youtube.com/watch?v=TIC6ZAkWoXY) -You are welcomed to help us do translations in more languages! (see section) +### Languages Supported -## Support Developer +* English (en) +* Chinese (Hong Kong) 中文 (香港) (zh-HK) +* Chinese (Simplified) 简体中文 (zh-CN) +* German Deutsch (de) +* French Français (fr) +* Italian Italiano (it) +* Portuguese (Brazil) (pt-BR) +* Russian Русский (ru) -To support this project, you can buy me a milk tea by making a donation. (Click me 😊) - -Thanks for your support! +You are welcomed to help translate the app into more languages (refer to this section) ## Contribute -- Star the project -- Do translation for different languages -- Open issue for bug reports -- Email me for any ideas regarding Simple QR or Project Simple +* Sponsor the project. -### Build the project (Android) + [![GitHub Sponsor](https://img.shields.io/badge/sponsor-30363D?style=flat&logo=GitHub-Sponsors&logoColor=#white)](https://github.com/sponsors/tomfong?frequency=one-time) + [![Buy me a Coffee](https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=flat&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/tomfong) -- Run ```npm install``` to install all dependencies -- Run ```npm run build:an``` -- In ```android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml```, add ```android:exported="true"``` inside receiver tag. -- In ```android/capacitor-cordova-android-plugins/src/main/java/nl/xservices/plugins/SocialSharing.java```, change line 274 to +* Star the project. - ```java - final PendingIntent pendingIntent = PendingIntent.getBroadcast(cordova.getActivity().getApplicationContext(), 0, receiverIntent, PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE); - ``` + [![Stars](https://img.shields.io/github/stars/tomfong/simple-qr?style=flat)](https://github.com/tomfong/simple-qr/stargazers) -- In ```android/app/src/main/res/values/styles.xml```, change +* Open issues to report bugs or share any new ideas. - ```xml - - ``` + [![Issues](https://img.shields.io/github/issues/tomfong/simple-qr?style=flat)](https://github.com/tomfong/simple-qr/issues) -### How to help to do translation? +* Translate the app into different languages. + +### How to help translate? -0. (Optional) Clone or fork this project 1. Take a look at this [JSON](https://github.com/tomfong/simple-qr/blob/master/src/assets/i18n/en.json) -2. Copy the JSON, rename it to locale.json, e.g. ja.json for Japanese, de.json for German -3. Change the values of each key. Try to stick to the meaning of the original wordings. DO NOT change key names. -4. Commit it (please place the JSON in the same directory, i.e. src/assets/i18n), or [email]('mailto:tomfong.dev@gmail.com') me the JSON. +2. Download it, rename it to locale.json, e.g. ja.json for Japanese, de.json for German +3. Change the values of each key. + * Try to stick to the meaning of the original wordings. + * Preserve special characters, e.g. ```

\n```. + * Preserve wordings with { }, e.g. ```{secret}```. + * Preserve wordings with \" \", e.g. ```\"cozmo/jsQR\"```. + * DO NOT change the key names. + * DO NOT change the order. +4. Email the JSON to me (tomfong.dev@gmail.com) after you finish. -## Changelogs +### Build the project -### 2.3.0 +1. Run ```npm install``` to install all dependencies. +2. Run ```npm run build``` -- Release iOS version -- UI updated -- Improved performance and fixed known issues +### Contributors -#### 2.2.0 +Thank you the following contributors who have made the app better! -- New "Backup and Restore" feature -- Minor UI update -- Improved performance and fixed known issues - -#### 2.1.0 - 2.1.2 - -- Correctly display Traditional Chinese if system language is Yue -- Simplified Chinese is supported now -- UI updated: Android native toast, new button layout and more! -- Back button is available on all pages -- Barcode type can be shown after scanning -- Improved performance and fixed known issues - -#### 2.0.2 - -- Fixed QR code sharing crash issue - -#### 2.0.0 - 2.0.1 - -- Changed to new scanning engine (capacitor-community/barcode-scanner) to solve issue occurred in Android 12 -- Removed camera pause feature due to technical problems -- Improved performance and fix issues - -#### 1.5.0 - -- Support Black color theme -- Support zooming in QR code -- Vibration effect update -- Minor UI update -- Improve performance and fix issues - -#### 1.4.0 - -- Migrated the app to Capacitor -- Improve experience when using "Special Actions" feature in Result page -- Show barcode type when scanning -- Improve performance and fix issues - -#### 1.3.0 - 1.3.3 - -- Redesign UI/UX -- Add vibration on/off setting -- Remove WiFi connection feature -- Remove ads -- Improve performance and fix issues - -#### 1.2.0 - 1.2.1 - -- Support image scanning to read QR code -- UI updated -- Improve performance and fix issues - -#### 1.1.0 - 1.1.5 - -- Support 1D Barcode, Aztec Code, Data Matrix Code and PDF417 Barcode scanning -- Support generating QR code with templates (Free Text, Email, Phone, Message, URL, vCard Contact and Wi-Fi) -- Improve performance of loading records on History page - -#### 1.0.2 - -- Removed in-app purchase - -#### 1.0.1 - -- Support Android 6.0 or above devices - -#### 1.0.0 - -- The first release version. Thanks for your support! Please feel free to rate the app and leave comments. +| Name | GitHub | How? | +|:-:|:-:|:-:| +| mondstern | [mondlicht-und-sterne](https://github.com/mondlicht-und-sterne) | German language translation | +| Valentino Bocchetti | [luftmensch-luftmensch](https://github.com/luftmensch-luftmensch) | Italian language translation | +| Smooth-E | [Smooth-E](https://github.com/Smooth-E) | Russian language translation | +| Daniel Ribeiro | [drcsj](https://github.com/drcsj) | Portuguese (Brazil) language translation | ## Framework ```sh - Ionic CLI : 6.19.0 - Ionic Framework : @ionic/angular 6.1.2 - @angular-devkit/build-angular : 13.3.3 - @angular-devkit/schematics : 13.3.3 - @angular/cli : 13.3.3 - @ionic/angular-toolkit : 6.1.0 + Ionic CLI : 7.2.0 + Ionic Framework : @ionic/angular 7.8.2 + @angular-devkit/build-angular : 16.2.13 + @angular-devkit/schematics : 16.2.13 + @angular/cli : 16.2.13 + @ionic/angular-toolkit : 9.0.0 - Capacitor CLI : 3.4.3 - @capacitor/android : 3.4.3 - @capacitor/core : 3.5.0 - @capacitor/ios : 3.5.0 - - Cordova CLI : 10.0.0 - - Android SDK Tools : 26.1.1 - NodeJS : v14.15.4 - npm : 6.14.10 + Capacitor CLI : 5.7.4 + @capacitor/android : 5.7.4 + @capacitor/core : 5.7.4 + @capacitor/ios : 5.7.4 ``` ## Privacy Policy @@ -206,4 +142,4 @@ Please read the [Privacy Policy](https://www.privacypolicies.com/live/771b1123-9 ## License -Please view the [LICENSE](LICENSE) +[![License](https://img.shields.io/github/license/tomfong/simple-qr?style=flat)](https://github.com/tomfong/simple-qr/blob/main/LICENSE) diff --git a/android/.idea/compiler.xml b/android/.idea/compiler.xml index fb7f4a8..b589d56 100644 --- a/android/.idea/compiler.xml +++ b/android/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/android/.idea/jarRepositories.xml b/android/.idea/jarRepositories.xml index e34606c..4dcbce1 100644 --- a/android/.idea/jarRepositories.xml +++ b/android/.idea/jarRepositories.xml @@ -26,5 +26,10 @@

- - - - - - - - - - {{ qrCodeContent }} - - - -
- - - - - - - - - - - - - \ No newline at end of file diff --git a/src/app/components/qrcode/qrcode.component.ts b/src/app/components/qrcode/qrcode.component.ts deleted file mode 100644 index 1949c5d..0000000 --- a/src/app/components/qrcode/qrcode.component.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; -import { Haptics, ImpactStyle } from '@capacitor/haptics'; -import { LoadingController, ModalController } from '@ionic/angular'; -import { TranslateService } from '@ngx-translate/core'; -import { NgxQrcodeElementTypes, NgxQrcodeErrorCorrectionLevels } from '@techiediaries/ngx-qrcode'; -import { EnvService } from 'src/app/services/env.service'; - -@Component({ - selector: 'app-qrcode', - templateUrl: './qrcode.component.html', - styleUrls: ['./qrcode.component.scss'], -}) -export class QrcodeComponent { - - @Input() qrCodeContent: string; - qrElementType: NgxQrcodeElementTypes = NgxQrcodeElementTypes.CANVAS; - errorCorrectionLevel: NgxQrcodeErrorCorrectionLevels = NgxQrcodeErrorCorrectionLevels.LOW; - qrMargin: number = 3; - - qrImageDataUrl: string; - - constructor( - private translate: TranslateService, - public env: EnvService, - private loadingController: LoadingController, - private modalController: ModalController, - private socialSharing: SocialSharing, - ) { } - - async shareQrCode(): Promise { - const loading = await this.presentLoading(this.translate.instant('PREPARING')); - const canvases = document.querySelectorAll("canvas") as NodeListOf; - const canvas = canvases[canvases.length - 1]; - if (this.qrImageDataUrl) { - delete this.qrImageDataUrl; - } - this.qrImageDataUrl = canvas.toDataURL("image/png", 0.8); - loading.dismiss(); - await this.socialSharing.share(this.translate.instant('MSG.SHARE_QR'), this.translate.instant('SIMPLE_QR'), this.qrImageDataUrl, null).then( - _ => { - delete this.qrImageDataUrl; - } - ).catch( - err => { - delete this.qrImageDataUrl; - } - ); - } - - async close() { - this.modalController.dismiss(); - } - - get qrColorDark(): string { - return "#222428"; - } - - get qrColorLight(): string { - return "#ffffff"; - } - - async presentLoading(msg: string): Promise { - const loading = await this.loadingController.create({ - message: msg, - mode: "ios" - }); - await loading.present(); - return loading; - } - - get color() { - switch (this.env.colorTheme) { - case 'dark': - return 'dark'; - case 'light': - return 'white'; - case 'black': - return 'black'; - default: - return 'white'; - } - } - - async tapHaptic() { - if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { - await Haptics.impact({ style: ImpactStyle.Medium }); - } - } -} diff --git a/src/app/modals/history-tutorial/history-tutorial.page.html b/src/app/modals/history-tutorial/history-tutorial.page.html deleted file mode 100644 index 2487859..0000000 --- a/src/app/modals/history-tutorial/history-tutorial.page.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - {{ 'TUTORIAL' | translate }} - - - - - - - - - - - - - - - -

- - {{ 'TUTORIAL_TOUCH_HISTORY' | translate }} - -

-
-
- - - - - -

- - {{ 'TUTORIAL_TOUCH_BOOKMARK' | translate }} - -

-
-
- - - - -

- - {{ 'TUTORIAL_SWIPE_LEFT' | translate }} - -

-
-
- - - - -

- - {{ 'TUTORIAL_SWIPE_RIGHT' | translate }} - -

-
-
- -
- - - - - - -

- - {{ 'TUTORIAL_NOT_SHOW_AGAIN' | translate }} - -

-
-
- -
\ No newline at end of file diff --git a/src/app/modals/history-tutorial/history-tutorial.page.scss b/src/app/modals/history-tutorial/history-tutorial.page.scss deleted file mode 100644 index 03b4b22..0000000 --- a/src/app/modals/history-tutorial/history-tutorial.page.scss +++ /dev/null @@ -1,14 +0,0 @@ -mat-form-field { - width: 100%; -} - -.content-item { - padding-left: 16px; - padding-right: 16px; -} - -ion-footer { - &.footer-md::before { - background-image: none; - } -} \ No newline at end of file diff --git a/src/app/modals/history-tutorial/history-tutorial.page.ts b/src/app/modals/history-tutorial/history-tutorial.page.ts deleted file mode 100644 index 41b89a4..0000000 --- a/src/app/modals/history-tutorial/history-tutorial.page.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; -import { ModalController, ToastController } from '@ionic/angular'; -import { TranslateService } from '@ngx-translate/core'; -import { EnvService } from 'src/app/services/env.service'; - -@Component({ - selector: 'app-history-tutorial', - templateUrl: './history-tutorial.page.html', - styleUrls: ['./history-tutorial.page.scss'], -}) -export class HistoryTutorialPage { - - @ViewChild('content') contentEl: HTMLIonContentElement; - - constructor( - public modalController: ModalController, - public translate: TranslateService, - public env: EnvService, - ) { - setTimeout( - () => { - this.contentEl.scrollToBottom(500); - }, 750 - ); - } - - async saveTutorialShowing() { - if (this.env.notShowHistoryTutorial === true) { - await this.env.storageSet("not-show-history-tutorial", 'yes'); - } else { - await this.env.storageSet("not-show-history-tutorial", 'no'); - } - } - - closeModal(): void { - this.modalController.dismiss(); - } - - get color() { - switch (this.env.colorTheme) { - case 'dark': - return 'dark'; - case 'light': - return 'white'; - case 'black': - return 'black'; - default: - return 'white'; - } - } - -} diff --git a/src/app/modals/history-tutorial/history-tutorial.module.ts b/src/app/modals/qr-code/qr-code.module.ts similarity index 62% rename from src/app/modals/history-tutorial/history-tutorial.module.ts rename to src/app/modals/qr-code/qr-code.module.ts index 5452d6e..68de60d 100644 --- a/src/app/modals/history-tutorial/history-tutorial.module.ts +++ b/src/app/modals/qr-code/qr-code.module.ts @@ -4,14 +4,11 @@ import { FormsModule } from '@angular/forms'; import { IonicModule } from '@ionic/angular'; -import { HistoryTutorialPage } from './history-tutorial.page'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; +import { QrCodePage } from './qr-code.page'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { HttpClient } from '@angular/common/http'; +import { QRCodeModule } from 'angularx-qrcode'; export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); @@ -22,10 +19,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { CommonModule, FormsModule, IonicModule, - MatFormFieldModule, - MatIconModule, - MatInputModule, - MatSelectModule, + QRCodeModule, TranslateModule.forChild({ loader: { provide: TranslateLoader, @@ -34,6 +28,6 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { } }), ], - declarations: [HistoryTutorialPage] + declarations: [QrCodePage] }) -export class HistoryTutorialPageModule {} +export class QrCodePageModule {} diff --git a/src/app/modals/qr-code/qr-code.page.html b/src/app/modals/qr-code/qr-code.page.html new file mode 100644 index 0000000..a2ef0f2 --- /dev/null +++ b/src/app/modals/qr-code/qr-code.page.html @@ -0,0 +1,94 @@ + + + + + {{ 'SHARE' | translate }} + + + + + +
+ + + + +
+ +
+
+
+ + + + {{ qrCodeContent }} + + + + + + +

+ +

+
+ +

+
+
+ + + +

+ + {{ 'LEVEL_L' | translate }} + +

+
+ + +
+ + +

+ + {{ 'LEVEL_M' | translate }} + +

+
+ + +
+ + +

+ + {{ 'LEVEL_Q' | translate }} + +

+
+ + +
+ + +

+ + {{ 'LEVEL_H' | translate }} + +

+
+ + +
+
+ + {{ 'QR_CODE_STYLE' | translate }} + +
+
+
\ No newline at end of file diff --git a/src/app/components/qrcode/qrcode.component.scss b/src/app/modals/qr-code/qr-code.page.scss similarity index 82% rename from src/app/components/qrcode/qrcode.component.scss rename to src/app/modals/qr-code/qr-code.page.scss index fc8dd01..90017fc 100644 --- a/src/app/components/qrcode/qrcode.component.scss +++ b/src/app/modals/qr-code/qr-code.page.scss @@ -6,6 +6,6 @@ font-size: medium; } -.footer-ios ion-toolbar:first-of-type { +ion-toolbar:first-of-type { --border-width: 0 !important; } \ No newline at end of file diff --git a/src/app/modals/qr-code/qr-code.page.ts b/src/app/modals/qr-code/qr-code.page.ts new file mode 100644 index 0000000..2b734f3 --- /dev/null +++ b/src/app/modals/qr-code/qr-code.page.ts @@ -0,0 +1,248 @@ +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; +import { ScreenOrientation } from '@awesome-cordova-plugins/screen-orientation/ngx'; +import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Toast } from '@capacitor/toast'; +import { LoadingController, ModalController, Platform } from '@ionic/angular'; +import { TranslateService } from '@ngx-translate/core'; +import { EnvService } from 'src/app/services/env.service'; +import { ScreenBrightness } from '@capacitor-community/screen-brightness'; +import { rgbToHex } from 'src/app/utils/helpers'; +import { Preferences } from '@capacitor/preferences'; +import { QRCodeComponent, QRCodeElementType } from 'angularx-qrcode'; + +@Component({ + selector: 'app-qr-code', + templateUrl: './qr-code.page.html', + styleUrls: ['./qr-code.page.scss'], +}) +export class QrCodePage { + + modal: HTMLIonModalElement; + + @ViewChild('qrcode') qrcodeElement: QRCodeComponent; + + @Input() qrCodeContent: string; + qrElementType: QRCodeElementType = "canvas"; + errorCorrectionLevel: 'low' | 'medium' | 'quartile' | 'high' | 'L' | 'M' | 'Q' | 'H'; + scale: number = 0.8; + readonly MAX_WIDTH = 350; + defaultWidth: number = window.innerHeight * 0.32 > this.MAX_WIDTH ? this.MAX_WIDTH : window.innerHeight * 0.32; + + qrImageDataUrl: string; + + currentBrightness: number = 0; + + isSharing: boolean = false; + + constructor( + private translate: TranslateService, + public env: EnvService, + private loadingController: LoadingController, + private modalController: ModalController, + private socialSharing: SocialSharing, + private router: Router, + private platform: Platform, + private screenOrientation: ScreenOrientation, + ) { + this.setErrorCorrectionLevel(); + } + + async ionViewDidEnter(): Promise { + this.isSharing = false; + this.platform.ready().then(async () => { + if (this.screenOrientation.type.startsWith(this.screenOrientation.ORIENTATIONS.LANDSCAPE)) { + this.presentToast(this.translate.instant("MSG.PORTRAIT_ONLY"), "short", "bottom"); + this.screenOrientation.unlock(); + } + await this.screenOrientation.lock(this.screenOrientation.ORIENTATIONS.PORTRAIT).then( + _ => { + if (this.qrcodeElement != null) { + setTimeout(() => { + this.qrcodeElement.width = this.platform.height() * this.scale * 0.4; + if (this.qrcodeElement.width > this.MAX_WIDTH) { + this.qrcodeElement.width = this.MAX_WIDTH; + } + }, 500) + } + } + ) + await ScreenBrightness.getBrightness().then( + value => { + this.currentBrightness = value.brightness + } + ) + if (this.env.autoMaxBrightness === 'on') { + await ScreenBrightness.setBrightness({ brightness: 1.0 }).catch( + err => { + if (this.env.isDebugging) { + this.presentToast("Err when ScreenBrightness.setBrightness 1.0: " + JSON.stringify(err), "long", "top"); + } + } + ) + } + await this.modalController.getTop().then( + async (modal: HTMLIonModalElement) => { + this.modal = modal; + this.modal.addEventListener("ionBreakpointDidChange", (ev: any) => { + if (this.qrcodeElement != null) { + switch (ev.detail.breakpoint) { + case 1: + this.qrcodeElement.width = this.platform.width() * this.scale; + break; + case 0.5: + this.qrcodeElement.width = this.platform.height() * this.scale * 0.4; + break + } + if (this.qrcodeElement.width > this.MAX_WIDTH) { + this.qrcodeElement.width = this.MAX_WIDTH; + } + } + }) + this.modal.onDidDismiss().then( + async _ => { + if (this.platform.is('android')) { + await ScreenBrightness.setBrightness({ brightness: -1 }).catch( + err => { + if (this.env.isDebugging) { + this.presentToast("Err when ScreenBrightness.setBrightness -1: " + JSON.stringify(err), "long", "top"); + } + } + ) + } else if (this.platform.is('ios')) { + await ScreenBrightness.setBrightness({ brightness: this.currentBrightness }).catch( + err => { + if (this.env.isDebugging) { + this.presentToast(`Err when ScreenBrightness.setBrightness ${this.currentBrightness}: ` + JSON.stringify(err), "long", "top"); + } + } + ) + } + await this.env.toggleOrientationChange(); + } + ); + } + ) + }); + } + + setErrorCorrectionLevel() { + switch (this.env.errorCorrectionLevel) { + case 'L': + this.errorCorrectionLevel = 'low'; + break; + case 'M': + this.errorCorrectionLevel = 'medium'; + break; + case 'Q': + this.errorCorrectionLevel = 'quartile'; + break; + case 'H': + this.errorCorrectionLevel = 'high'; + break; + default: + this.errorCorrectionLevel = 'medium'; + } + } + + async onErrorCorrectionLevelChange() { + this.setErrorCorrectionLevel(); + await Preferences.set({ key: this.env.KEY_ERROR_CORRECTION_LEVEL, value: this.env.errorCorrectionLevel }); + if (this.qrcodeElement != null) { + this.qrcodeElement.errorCorrectionLevel = this.errorCorrectionLevel; + } else { + if (this.env.isDebugging) { + this.presentToast("Cannot ref qrcodeElement!", "long", "top"); + } + } + } + + goQrSetting() { + this.modalController.dismiss(); + this.router.navigate(['setting-qr']); + } + + async shareQrCode(): Promise { + const loading = await this.presentLoading(this.translate.instant('PREPARING')); + this.isSharing = true; + const currentWidth = this.qrcodeElement.width; + this.qrcodeElement.width = 1000; + setTimeout(async () => { + const canvases = document.querySelectorAll("canvas") as NodeListOf; + const canvas = canvases[canvases.length - 1]; + if (this.qrImageDataUrl) { + delete this.qrImageDataUrl; + } + this.qrImageDataUrl = canvas.toDataURL("image/png", 1); + loading.dismiss(); + const loading2 = await this.presentLoading(this.translate.instant('SHARING')); + await this.socialSharing.share(this.translate.instant('MSG.SHARE_QR'), this.translate.instant('SIMPLE_QR'), this.qrImageDataUrl, null).then( + _ => { + this.qrcodeElement.width = currentWidth; + delete this.qrImageDataUrl; + this.isSharing = false; + loading2.dismiss(); + } + ).catch( + err => { + if (this.env.isDebugging) { + this.presentToast("Error when call SocialSharing.share: " + JSON.stringify(err), "long", "top"); + } + this.qrcodeElement.width = currentWidth; + delete this.qrImageDataUrl; + this.isSharing = false; + loading2.dismiss(); + } + ); + }, 500) + } + + get qrColorDark(): string { + return rgbToHex(this.env.qrCodeDarkR, this.env.qrCodeDarkG, this.env.qrCodeDarkB); + } + + get qrColorLight(): string { + return rgbToHex(this.env.qrCodeLightR, this.env.qrCodeLightG, this.env.qrCodeLightB); + } + + async presentLoading(msg: string): Promise { + const loading = await this.loadingController.create({ + message: msg + }); + await loading.present(); + return loading; + } + + get color() { + switch (this.env.colorTheme) { + case 'dark': + return 'dark'; + case 'light': + return 'white'; + case 'black': + return 'black'; + default: + return 'white'; + } + } + + async presentToast(msg: string, duration: "short" | "long", pos: "top" | "center" | "bottom") { + await Toast.show({ + text: msg, + duration: duration, + position: pos + }); + } + + async tapHaptic() { + if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) + } + } +} diff --git a/src/app/models/bookmark.ts b/src/app/models/bookmark.ts index 5f2dc14..49f0f3a 100644 --- a/src/app/models/bookmark.ts +++ b/src/app/models/bookmark.ts @@ -1,4 +1,6 @@ export class Bookmark { + id: string; text: string; createdAt: Date; + tag: string; } \ No newline at end of file diff --git a/src/app/models/scan-record.ts b/src/app/models/scan-record.ts index 04793cd..0c56710 100644 --- a/src/app/models/scan-record.ts +++ b/src/app/models/scan-record.ts @@ -2,4 +2,6 @@ export class ScanRecord { id: string; text: string; createdAt: Date; + source: 'create' | 'view' | 'scan' | undefined; + barcodeType: string | undefined; } \ No newline at end of file diff --git a/src/app/pages/about/about.page.html b/src/app/pages/about/about.page.html index a28aac8..7f3ebd8 100644 --- a/src/app/pages/about/about.page.html +++ b/src/app/pages/about/about.page.html @@ -1,7 +1,7 @@ - - + + - + {{ 'ABOUT' | translate }} @@ -14,118 +14,78 @@

by

Tom Fong

- - + + -

+

{{ 'SUPPORTED_BARCODE_TYPE' | translate }}

- - + + -

+

- {{ 'UPDATE_NOTES' | translate }} + {{ 'PATCH_NOTES' | translate }}

- - - - -

- - {{ 'VIEW_SOURCE' | translate }} - - - -

-
-
- - + -

+

- {{ 'CHECK_GOOGLE_PLAY' | translate }} + {{ 'VIEW_STORE_AND_SOURCE_CODE' | translate }} - -

-
- - + + -

+

- {{ 'CHECK_APP_STORE' | translate }} + {{ 'VIEW_STORE_AND_SOURCE_CODE' | translate }} - -

-
- - + + -

+

{{ 'REPORT_ISSUE' | translate }} - -

-
- - + + -

+

{{ 'PRIVACY_POLICY' | translate }} - -

-
- + - - + +
{{ ('APP_VERSION' | translate) + ' ' + env.appVersionNumber }}
diff --git a/src/app/pages/about/about.page.ts b/src/app/pages/about/about.page.ts index 80f3a55..43afd39 100644 --- a/src/app/pages/about/about.page.ts +++ b/src/app/pages/about/about.page.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { Preferences } from '@capacitor/preferences'; import { Toast } from '@capacitor/toast'; import { AlertController, Platform } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; @@ -40,12 +41,11 @@ export class AboutPage { window.open(this.env.APP_STORE_URL, '_system'); } - async showBarcodeType() { const alert = await this.alertController.create({ header: this.translate.instant("SUPPORTED_TYPE"), message: this.translate.instant("MSG.BARCODE_TYPE"), - buttons: [this.translate.instant("OK")], + buttons: [this.translate.instant("CLOSE")], cssClass: ['left-align', 'alert-bg'] }); await alert.present(); @@ -53,30 +53,44 @@ export class AboutPage { async showUpdateNotes() { const alert = await this.alertController.create({ - header: this.translate.instant("UPDATE_NOTES"), - subHeader: this.env.appVersionNumber, - message: this.platform.is('ios')? this.translate.instant("UPDATE.UPDATE_NOTES_IOS") : this.translate.instant("UPDATE.UPDATE_NOTES_ANDROID"), - buttons: [this.translate.instant("OK")], + header: this.translate.instant("PATCH_NOTES"), + message: this.platform.is('ios') ? this.translate.instant("UPDATE.UPDATE_NOTES_IOS") : this.translate.instant("UPDATE.UPDATE_NOTES_ANDROID"), + buttons: [ + { + text: this.translate.instant("CLOSE"), + handler: () => true, + }, + { + text: this.translate.instant("VIEW_GITHUB"), + handler: () => { + this.openGitHubRelease(); + } + } + ], cssClass: ['left-align', 'alert-bg'] }); await alert.present(); } + openGitHubRelease() { + window.open(this.env.GITHUB_RELEASE_URL, '_system'); + } + viewPrivacyPolicy(): void { window.open(this.env.PRIVACY_POLICY, '_system'); } async reportBug() { - const mailContent = await this.env.getBugReportMailContent(); + const mailContent = this.env.getBugReportMailContent(); window.open(mailContent, '_system'); } async tapAppVersion() { this.tapAppVersionTimes++; - if (this.env.debugModeOn != 'on') { + if (this.env.debugMode != 'on') { if (this.tapAppVersionTimes >= 5) { - this.env.debugModeOn = 'on'; - await this.env.storageSet("debug-mode-on", 'on'); + this.env.debugMode = 'on'; + await Preferences.set({ key: this.env.KEY_DEBUG_MODE, value: 'on' }); await Toast.show({ text: this.translate.instant("MSG.DEBUG_MODE_ON"), duration: "short", diff --git a/src/app/pages/generate/generate.page.html b/src/app/pages/generate/generate.page.html index fb0b41a..9fe66cc 100644 --- a/src/app/pages/generate/generate.page.html +++ b/src/app/pages/generate/generate.page.html @@ -1,7 +1,16 @@ - - - {{ 'CREATE_QRCODE' | translate }} - + +
+
+ + + {{ 'SIMPLE_QR' | translate}} + + + + + {{ 'CREATE_QR_CODE' | translate }} + +
@@ -9,27 +18,29 @@ - {{ 'CREATE_QRCODE_MAX_LENGTH_EXPLAIN' | translate}} + {{ 'MSG.CREATE_QRCODE_MAX_LENGTH_EXPLAIN' | translate}} - + {{ 'CONTENT_TYPE' | translate}} -
- - {{ getIcon(contentType) }} +
+ {{ getText(contentType) }}
- {{ getIcon(type.value) }} - {{ type.text }} +
+ + + {{ type.text }} +
@@ -37,36 +48,36 @@ - + - {{ 'QRCODE_CONTENT' | translate }} - + {{ 'QR_CODE_CONTENT' | translate }} + {{ freeTxtInput.value?.length || 0 }}/1817 - {{'CREATE_QRCODE_MAX_LENGTH' | translate}} + {{'MSG.CREATE_QRCODE_MAX_LENGTH' | translate}} - + - + {{ 'EMAIL_RECIPIENT' | translate}} {{ emailToInput.value?.length || 0 }}/254 - alternate_email - {{'EMAIL_MAX_LENGTH' | translate}} + + {{'MSG.EMAIL_MAX_LENGTH' | translate}} - + @@ -83,19 +94,19 @@ - + {{ 'CC' | translate}} {{ emailCcInput.value?.length || 0 }}/254 - alternate_email - {{'EMAIL_MAX_LENGTH' | translate}} + + {{'MSG.EMAIL_MAX_LENGTH' | translate}} - + @@ -112,19 +123,19 @@ - + {{ 'BCC' | translate}} {{ emailBccInput.value?.length || 0 }}/254 - alternate_email - {{'EMAIL_MAX_LENGTH' | translate}} + + {{'MSG.EMAIL_MAX_LENGTH' | translate}} - + @@ -139,75 +150,134 @@ - + {{ 'EMAIL_SUBJECT' | translate}} {{ emailSubjectInput.value?.length || 0 }}/78 - {{'EMAIL_SUBJECT_MAX_LENGTH' | translate}} + {{'MSG.EMAIL_SUBJECT_MAX_LENGTH' | translate}} - + {{ 'EMAIL_BODY' | translate}} {{ emailBodyInput.value?.length || 0 }}/1817 - {{'CREATE_QRCODE_MAX_LENGTH' | translate}} + {{'MSG.CREATE_QRCODE_MAX_LENGTH' | translate}} + + + + + + + {{ 'EMAIL_RECIPIENT' | translate}} + + {{ emailToInput.value?.length || 0 }}/254 + + {{'MSG.EMAIL_MAX_LENGTH' | translate}} + + + + + + + + {{ 'EMAIL_SUBJECT' | translate}} + + {{ emailSubjectInput.value?.length || 0 }}/78 + {{'MSG.EMAIL_SUBJECT_MAX_LENGTH' | translate}} + + + + + + + + {{ 'EMAIL_BODY' | translate}} + + {{ emailBodyInput.value?.length || 0 }}/1817 + {{'MSG.CREATE_QRCODE_MAX_LENGTH' | translate}} + + + + + + + + + + + {{ 'LATITUDE' | translate}} + + + + + + + + {{ 'LONGITUDE' | translate}} + + + + + + - + {{ 'PHONE_NUMBER' | translate}} - call + - + {{ 'PHONE_NUMBER' | translate}} - call + - + - {{ 'SMS' | translate}} + {{ 'MESSAGE' | translate}} {{ smsInput.value?.length || 0 }}/160 - {{'SMS_MAX_LENGTH' | translate}} + {{'MSG.SMS_MAX_LENGTH' | translate}} - + {{ 'URL' | translate}} - link + {{ (urlInput.value?.length) || 0 }}/1817 - {{'CREATE_QRCODE_MAX_LENGTH' | translate}} + {{'MSG.CREATE_QRCODE_MAX_LENGTH' | translate}} @@ -224,21 +294,21 @@ - + {{ 'FIRST_NAME' | translate}} - badge + - + {{ 'LAST_NAME' | translate}} - badge + @@ -252,48 +322,48 @@ - + {{ 'MOBILE_PHONE_NUMBER' | translate}} - call + - + {{ 'HOME_PHONE_NUMBER' | translate}} - call + - + {{ 'WORK_PHONE_NUMBER' | translate}} - call + - + {{ 'FAX_NUMBER' | translate}} - print + - + {{ 'EMAIL_ADDRESS' | translate}} - alternate_email + @@ -307,21 +377,21 @@ - + {{ 'ORGANIZATION' | translate}} - business + - + {{ 'JOB_TITLE' | translate}} - business + @@ -335,48 +405,48 @@ - + {{ 'STREET' | translate}} - home + - + {{ 'CITY' | translate}} - home + - + {{ 'STATE' | translate}} - home + - + {{ 'POSTAL_CODE' | translate}} - home + - + {{ 'COUNTRY' | translate}} - home + @@ -390,17 +460,17 @@ - + - {{ 'BIRTHDAY' | translate}} + {{ 'DATE_OF_BIRTH' | translate}} - cake + - + {{ 'GENDER' | translate}} @@ -412,39 +482,39 @@ - + {{ 'WEBSITE' | translate}} - link + - + {{ 'WIFI_SSID' | translate}} - wifi + {{ (ssidInput.value?.length) || 0 }}/32 - {{'SSID_MAX_LENGTH' | translate}} + {{'MSG.SSID_MAX_LENGTH' | translate}} - + {{ 'PASSWORD' | translate}} - lock + - + {{ 'WIFI_ENCRYPTION' | translate}} @@ -456,23 +526,23 @@ - + - - {{ 'WIFI_HIDDEN' | translate }} - + + {{ 'HIDDEN_NETWORK_?' | translate }} + - - + {{ 'CLEAR' | translate }} - + {{ 'CREATE' | translate }} diff --git a/src/app/pages/generate/generate.page.ts b/src/app/pages/generate/generate.page.ts index fe7f8d9..8056be8 100644 --- a/src/app/pages/generate/generate.page.ts +++ b/src/app/pages/generate/generate.page.ts @@ -4,31 +4,17 @@ import { Router } from '@angular/router'; import { Haptics, ImpactStyle, NotificationType } from '@capacitor/haptics'; import { AlertController, LoadingController, ToastController } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; -import * as moment from 'moment'; -import { EnvService } from 'src/app/services/env.service'; +import { format } from 'date-fns'; +import { EnvService, QrCreateContentTypeType } from 'src/app/services/env.service'; import { Toast } from '@capacitor/toast'; +import { fadeIn } from 'src/app/utils/animations'; +import { SplashScreen } from '@capacitor/splash-screen'; @Component({ selector: 'app-generate', templateUrl: './generate.page.html', styleUrls: ['./generate.page.scss'], - animations: [ - trigger( - 'inOutAnimation', - [ - transition( - ':enter', - [ - style({ opacity: 0 }), - animate( - '1s ease', - style({ opacity: 1 }) - ) - ] - ) - ] - ) - ] + animations: [fadeIn] }) export class GeneratePage { @@ -37,9 +23,11 @@ export class GeneratePage { freeTxtText: string = "Free Text"; urlText: string = "URL"; contactText: string = "vCard Contact"; + geolocationText: string = "Geolocation"; phoneText: string = "Phone"; smsText: string = "Message"; - emailText: string = "Email"; + emailW3CText: string = "Email (W3C Standard)"; + emailDocomoText: string = "Email (NTT Docomo)"; wifiText: string = "WiFi"; qrCodeContent: string = ""; @@ -50,6 +38,9 @@ export class GeneratePage { emailSubject: string = ""; emailBody: string = ""; + latitude: number = 0; + longitude: number = 0; + phoneNumber: string = ""; smsMessage: string = ""; @@ -70,7 +61,7 @@ export class GeneratePage { state: string = ""; postalCode: string = ""; country: string = ""; - birthday: Date; + birthday: string; gender: "M" | "F" | "O" = "O"; personalUrl: string = ""; @@ -97,16 +88,18 @@ export class GeneratePage { { text: this.wpaText, value: "WPA" }, ] - contentTypes: { text: string, value: "freeText" | "url" | "contact" | "phone" | "sms" | "email" | "wifi" }[] = [ + contentTypes: { text: string, value: QrCreateContentTypeType }[] = [ { text: this.freeTxtText, value: 'freeText' }, - { text: this.emailText, value: 'email' }, + { text: this.emailW3CText, value: 'emailW3C' }, + { text: this.emailDocomoText, value: 'emailDocomo' }, + { text: this.geolocationText, value: 'geo' }, { text: this.phoneText, value: 'phone' }, { text: this.smsText, value: 'sms' }, { text: this.urlText, value: 'url' }, { text: this.contactText, value: 'contact' }, { text: this.wifiText, value: 'wifi' }, ]; - contentType: "freeText" | "url" | "contact" | "phone" | "sms" | "email" | "wifi" = "freeText"; + contentType: QrCreateContentTypeType = "freeText"; constructor( public translate: TranslateService, @@ -114,17 +107,28 @@ export class GeneratePage { public alertController: AlertController, public loadingController: LoadingController, private router: Router, - ) { + ) { } + + async ionViewDidEnter() { + await SplashScreen.hide() + if (this.env.editingContent) { + this.qrCodeContent = this.env.resultContent; + this.env.editingContent = false; + } this.freeTxtText = this.translate.instant("FREE_TEXT"); this.urlText = this.translate.instant("URL"); this.contactText = this.translate.instant("VCARD_CONTACT"); - this.phoneText = this.translate.instant("PHONE"); - this.smsText = this.translate.instant("SMS"); - this.emailText = this.translate.instant("EMAIL"); + this.geolocationText = this.translate.instant("GEOLOCATION"); + this.phoneText = this.translate.instant("PHONE_NO"); + this.smsText = this.translate.instant("MESSAGE"); + this.emailW3CText = this.translate.instant("EMAIL_W3C_STANDARD"); + this.emailDocomoText = this.translate.instant("EMAIL_NTT_DOCOMO"); this.wifiText = this.translate.instant("WIFI"); this.contentTypes = [ { text: this.freeTxtText, value: 'freeText' }, - { text: this.emailText, value: 'email' }, + { text: this.emailW3CText, value: 'emailW3C' }, + { text: this.emailDocomoText, value: 'emailDocomo' }, + { text: this.geolocationText, value: 'geo' }, { text: this.phoneText, value: 'phone' }, { text: this.smsText, value: 'sms' }, { text: this.urlText, value: 'url' }, @@ -147,37 +151,8 @@ export class GeneratePage { ]; } - ionViewDidEnter() { - this.freeTxtText = this.translate.instant("FREE_TEXT"); - this.urlText = this.translate.instant("URL"); - this.contactText = this.translate.instant("VCARD_CONTACT"); - this.phoneText = this.translate.instant("PHONE"); - this.smsText = this.translate.instant("SMS"); - this.emailText = this.translate.instant("EMAIL"); - this.wifiText = this.translate.instant("WIFI"); - this.contentTypes = [ - { text: this.freeTxtText, value: 'freeText' }, - { text: this.emailText, value: 'email' }, - { text: this.phoneText, value: 'phone' }, - { text: this.smsText, value: 'sms' }, - { text: this.urlText, value: 'url' }, - { text: this.contactText, value: 'contact' }, - { text: this.wifiText, value: 'wifi' }, - ]; - this.noneGenderText = this.translate.instant("NOT_TO_DISCLOSE"); - this.maleText = this.translate.instant("MALE"); - this.femaleText = this.translate.instant("FEMALE"); - this.genders = [ - { text: this.noneGenderText, value: "O" }, - { text: this.maleText, value: "M" }, - { text: this.femaleText, value: "F" }, - ] - this.noneWifiEncText = this.translate.instant("NONE"); - this.wifiEncryptions = [ - { text: this.noneWifiEncText, value: "nopass" }, - { text: this.wepText, value: "WEP" }, - { text: this.wpaText, value: "WPA" }, - ]; + ionViewDidLeave() { + this.clear(); } trackByIndex(index: number, obj: any): any { @@ -227,6 +202,9 @@ export class GeneratePage { this.emailSubject = ""; this.emailBody = ""; + this.latitude = 0; + this.longitude = 0; + this.phoneNumber = ""; this.smsMessage = ""; @@ -259,7 +237,7 @@ export class GeneratePage { async goGenerate() { switch (this.contentType) { - case "email": + case "emailW3C": this.qrCodeContent = "mailto:"; this.toEmails.forEach((email, i, emails) => { emails[i] = email.trim(); @@ -291,6 +269,13 @@ export class GeneratePage { this.qrCodeContent += this.emailBody; this.qrCodeContent = encodeURI(this.qrCodeContent); break; + case "emailDocomo": + this.qrCodeContent = `MATMSG:TO:${this.toEmails[0]};SUB:${this.emailSubject};BODY:${this.emailBody};;`; + this.qrCodeContent = encodeURI(this.qrCodeContent); + break; + case "geo": + this.qrCodeContent = `geo:${this.latitude},${this.longitude}`; + break; case "phone": this.qrCodeContent = "tel:"; this.qrCodeContent += this.phoneNumber; @@ -319,7 +304,7 @@ export class GeneratePage { if ((this.qrCodeContent && this.qrCodeContent.trim().length <= 0) || this.qrCodeContent === "") { await this.presentToast(this.translate.instant('MSG.QR_CODE_VALUE_NOT_EMPTY'), "short", "bottom"); } else if (this.qrCodeContent.length > 1817) { - await this.presentToast(this.translate.instant('CREATE_QRCODE_MAX_LENGTH'), "short", "bottom"); + await this.presentToast(this.translate.instant('MSG.CREATE_QRCODE_MAX_LENGTH'), "short", "bottom"); } else { const loading = await this.presentLoading(this.translate.instant('PLEASE_WAIT')); await this.processQrCode(loading); @@ -327,10 +312,13 @@ export class GeneratePage { } async processQrCode(loading: HTMLIonLoadingElement): Promise { - this.env.result = this.qrCodeContent; - this.env.resultFormat = ""; + this.env.resultContent = this.qrCodeContent; + this.env.resultContentFormat = ""; this.qrCodeContent = ''; - this.router.navigate(['tabs/result', { from: 'generate', t: new Date().getTime() }], { state: { page: 'generate'}}).then( + this.env.recordSource = "create"; + this.env.detailedRecordSource = "create"; + this.env.viewResultFrom = "/tabs/generate"; + this.router.navigate(['tabs/result']).then( () => { loading.dismiss(); } @@ -349,9 +337,10 @@ export class GeneratePage { vCard += `ORG:${this.organization.trim()}\n`; vCard += `TITLE:${this.jobTitle.trim()}\n`; vCard += `ADR:;;${this.street.trim()};${this.city.trim()};${this.state.trim()};${this.postalCode.trim()};${this.country.trim()}\n`; - console.log("birthday => " + this.birthday) - if (this.birthday && this.birthday !== null && this.birthday !== undefined) { - vCard += `BDAY:${moment(this.birthday).format('YYYYMMDD')}\n`; + if (this.birthday != null) { + const find = '-'; + const re = new RegExp(find, 'g'); + vCard += `BDAY:${this.birthday.replace(re, "")}\n`; } vCard += `URL:${this.personalUrl.trim()}\n`; vCard += `GENDER:${this.gender}\n`; @@ -369,10 +358,10 @@ export class GeneratePage { } get today() { - return moment().format("YYYY-MM-DD"); + return format(new Date(), "yyyy-MM-dd"); } - getIcon(type: "freeText" | "url" | "contact" | "phone" | "sms" | "email" | "wifi"): string { + getIcon(type: QrCreateContentTypeType): string { switch (type) { case "freeText": return "format_align_left"; @@ -380,11 +369,15 @@ export class GeneratePage { return "link"; case "contact": return "contact_phone"; + case "geo": + return "location_on"; case "phone": return "call"; case "sms": return "sms"; - case "email": + case "emailW3C": + return "email"; + case "emailDocomo": return "email"; case "wifi": return "wifi"; @@ -393,7 +386,7 @@ export class GeneratePage { } } - getText(type: "freeText" | "url" | "contact" | "phone" | "sms" | "email" | "wifi"): string { + getText(type: QrCreateContentTypeType): string { switch (type) { case "freeText": return this.freeTxtText; @@ -401,12 +394,16 @@ export class GeneratePage { return this.urlText; case "contact": return this.contactText; + case "geo": + return this.geolocationText; case "phone": return this.phoneText; case "sms": return this.smsText; - case "email": - return this.emailText; + case "emailW3C": + return this.emailW3CText; + case "emailDocomo": + return this.emailDocomoText; case "wifi": return this.wifiText; default: @@ -438,8 +435,7 @@ export class GeneratePage { async presentLoading(msg: string): Promise { const loading = await this.loadingController.create({ - message: msg, - mode: "ios" + message: msg }); await loading.present(); return loading; @@ -468,7 +464,12 @@ export class GeneratePage { async tapHaptic() { if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { - await Haptics.impact({ style: ImpactStyle.Medium }); + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) } } } diff --git a/src/app/pages/history/history.page.html b/src/app/pages/history/history.page.html index 2291fc4..2860c02 100644 --- a/src/app/pages/history/history.page.html +++ b/src/app/pages/history/history.page.html @@ -1,120 +1,238 @@ - - - - {{ 'HISTORY' | translate }} - {{ 'BOOKMARKS' | translate }} - + +
+
+ + + {{ 'SIMPLE_QR' | translate}} + + + {{ 'MORE' | translate }} + + + + + + + {{ 'LOG' | translate }} + {{ 'BOOKMARKS' | translate }} + + + {{ 'MORE' | translate }} + + + +
- - - - - - - + + + + + + + + + + + + + + + + + - -
- -
+ + + + + + + +
+

+ + + +

+

+ + + +

+

+ + + +

+

+ + + +

+
+
+
+
+
- -
+ + + +
-

+

- {{ 'SCANNING_HISTORY_SHOWN' | translate }} + {{ 'MSG.PREVIOUS_RECORDS' | translate }}

- - - - - -

- - {{ record.text }} - -

-

- - {{ record.id }} - -

-

- - {{ maskDatetime(record.createdAt) }} - -

-
-
- - - - - - - - - - -
+ + + + + +
+ {{ 'NUMBER_OF_RECORDS' | translate }}: {{ env.scanRecords.length }} / {{ denominator }} +
+
+ {{ 'NUMBER_OF_RECORDS' | translate }}: {{ env.scanRecords.length }} +
+
+ + + + +
+

+ + Index {{ i }} + +

+

+ + {{ record.text }} + +

+

+ + {{ record.id }} + +

+

+ + {{ maskDatetimeAndSource(record.createdAt, record.source) }} + +

+ +
+ {{ getBarcodeFormat(record.barcodeType) }} +
+
+
+
+
+ + + + + + + + + + +
+
+ + + +
- -
- -

+ + + +

+ + +

- {{ 'FAVOURITE_TEXT_SHOWN' | translate }} + {{ 'BOOKMARKED_TEXTS' | translate }}

- - - - - -

- - {{ bookmark.text }} - -

-
- -
- - - - - -
+ + + + + + + + +
+

+ + Index {{ i }} + +

+

+ + {{ bookmark.tag? bookmark.tag : '-' }} + +

+

+ + {{ bookmark.text }} + +

+

+ + {{ bookmark.id }} + +

+
+
+
+ + + + + + + + + + +
+
+ + + +
- - + {{ 'REMOVE_ALL' | translate }} - - {{ 'SETTING' | translate }} - \ No newline at end of file diff --git a/src/app/pages/history/history.page.ts b/src/app/pages/history/history.page.ts index 1ee5698..43b106b 100644 --- a/src/app/pages/history/history.page.ts +++ b/src/app/pages/history/history.page.ts @@ -1,21 +1,22 @@ -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; -import { AlertController, IonItemSliding, LoadingController, ModalController, Platform, PopoverController, ToastController } from '@ionic/angular'; +import { ChangeDetectorRef, Component } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { AlertController, IonItemSliding, LoadingController, ModalController, PopoverController, ToastController } from '@ionic/angular'; import { EnvService } from 'src/app/services/env.service'; -import * as moment from 'moment'; +import { format, Locale } from 'date-fns'; +import { de, enUS, fr, it, ptBR, ru, zhCN, zhHK } from 'date-fns/locale'; import { ScanRecord } from 'src/app/models/scan-record'; import { TranslateService } from '@ngx-translate/core'; import { Bookmark } from 'src/app/models/bookmark'; -import { HistoryTutorialPage } from 'src/app/modals/history-tutorial/history-tutorial.page'; -import { MenuComponent } from 'src/app/components/menu/menu.component'; -import { MenuItem } from 'src/app/models/menu-item'; -import { Haptics, ImpactStyle, NotificationType } from '@capacitor/haptics'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; import { Toast } from '@capacitor/toast'; +import { fastFadeIn, flyOut } from 'src/app/utils/animations'; +import { SplashScreen } from '@capacitor/splash-screen'; @Component({ selector: 'app-history', templateUrl: './history.page.html', styleUrls: ['./history.page.scss'], + animations: [fastFadeIn, flyOut] }) export class HistoryPage { @@ -23,13 +24,11 @@ export class HistoryPage { deleteToast: HTMLIonToastElement; - scanRecords: ScanRecord[] = []; - bookmarks: Bookmark[] = []; + dummyArr = Array.from(Array(10).keys()); - firstLoad: boolean = true; + isLoading: boolean = false; constructor( - private platform: Platform, public alertController: AlertController, public loadingController: LoadingController, private router: Router, @@ -38,86 +37,271 @@ export class HistoryPage { public translate: TranslateService, public modalController: ModalController, public popoverController: PopoverController, - ) { } + private route: ActivatedRoute, + private changeDetectorRef: ChangeDetectorRef + ) { + this.route.params.subscribe(val => { + setTimeout(() => this.firstLoadItems(), 200); + }); + } - async loadItems() { - this.scanRecords = this.env.scanRecords; - this.bookmarks = this.env.bookmarks; + firstLoadItems() { + this.isLoading = true; + if (this.env.recordsLimit != -1) { + if (this.env.scanRecords.length > this.env.recordsLimit) { + this.env.scanRecords = this.env.scanRecords.slice(0, this.env.recordsLimit); + } + } + this.env.viewingScanRecords = []; + this.env.viewingBookmarks = []; + const scanRecords = [...this.env.scanRecords]; + this.env.viewingScanRecords = scanRecords.slice(0, 15); + const bookmarks = [...this.env.bookmarks]; + this.env.viewingBookmarks = bookmarks.slice(0, 15); + this.isLoading = false; + } + + loadMoreScanRecords() { + const scanRecords = [...this.env.scanRecords] + this.env.viewingScanRecords.push(...scanRecords.slice(this.env.viewingScanRecords.length, this.env.viewingScanRecords.length + 15)); + } + + loadMoreBookmarks() { + const bookmarks = [...this.env.bookmarks] + this.env.viewingBookmarks.push(...bookmarks.slice(this.env.viewingBookmarks.length, this.env.viewingBookmarks.length + 15)); + } + + onLoadScanRecords(ev: any) { + setTimeout(() => { + ev.target.complete(); + this.loadMoreScanRecords(); + if (this.env.viewingScanRecords.length === this.env.scanRecords.length) { + ev.target.disabled = true; + } + }, 500); + } + + onLoadBookmarks(ev: any) { + setTimeout(() => { + ev.target.complete(); + this.loadMoreBookmarks(); + if (this.env.viewingBookmarks.length === this.env.bookmarks.length) { + ev.target.disabled = true; + } + }, 500); + } + + ionViewWillEnter() { + this.isLoading = true; } async ionViewDidEnter() { - await this.loadItems(); - this.firstLoad = false; - if (this.env.notShowHistoryTutorial === false) { - this.env.notShowHistoryTutorial = true; - this.env.storageSet("not-show-history-tutorial", 'yes'); - await this.showTutorial(); - } + await SplashScreen.hide() + this.segmentModel = this.env.historyPageStartSegment; } - async ionViewWillLeave() { + ionViewWillLeave() { + this.changeDetectorRef.detach(); + this.env.viewingScanRecords = []; + this.env.viewingBookmarks = []; + this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.reattach(); if (this.deleteToast) { this.deleteToast.dismiss(); this.deleteToast = undefined; } } - async showTutorial() { - const modal = await this.modalController.create({ - component: HistoryTutorialPage, - cssClass: 'tutorial-modal-page', - componentProps: { - } - }); - modal.present(); + scanRecordsTrackByFn(index: number, record: ScanRecord): string { + return record.id; } - maskDatetime(date: Date): string { + bookmarksTrackByFn(index: number, bookmark: Bookmark): string { + return bookmark.id; + } + + maskDatetimeAndSource(date: Date, source: 'create' | 'view' | 'scan' | undefined): string { if (!date) { return "-"; } - if (this.translate.currentLang === 'zh-HK' || this.translate.currentLang === 'zh-CN') { - return moment(date).format("YYYY年M月D日 HH:mm:ss"); + let locale: Locale; + switch (this.env.language) { + case "de": + locale = de; + break; + case "en": + locale = enUS; + break; + case "fr": + locale = fr; + break; + case "it": + locale = it; + break; + case "pt-BR": + locale = ptBR; + break; + case "ru": + locale = ru; + break; + case "zh-CN": + locale = zhCN; + break; + case "zh-HK": + locale = zhHK; + break; + default: + locale = enUS; + } + switch (source) { + case 'create': + return `${this.translate.instant("CREATED")} ${this.translate.instant("AT")} ${format(date, "PP pp", { locale: locale })}`; + case 'view': + return `${this.translate.instant("VIEWED")} ${this.translate.instant("AT")} ${format(date, "PP pp", { locale: locale })}`; + case 'scan': + return `${this.translate.instant("SCANNED")} ${this.translate.instant("AT")} ${format(date, "PP pp", { locale: locale })}`; } - return moment(date).format("YYYY-MMM-DD HH:mm:ss"); } - async processQrCode(scannedData: string): Promise { + getBarcodeFormat(barcodeType: string): string { + switch (barcodeType) { + case "UPC_A": + return this.translate.instant("BARCODE_TYPE.UPC").trim() + ` (UPC-A)`; + case "UPC_E": + return this.translate.instant("BARCODE_TYPE.UPC").trim() + ` (UPC-E)`; + case "UPC_EAN_EXTENSION": + return this.translate.instant("BARCODE_TYPE.UPC").trim() + ` (UPC/EAN Ext.)`; + case "EAN_8": + return this.translate.instant("BARCODE_TYPE.EAN").trim() + ` (EAN-8)`; + case "EAN_13": + return this.translate.instant("BARCODE_TYPE.EAN").trim() + ` (EAN-13)`; + case "CODE_39": + return this.translate.instant("BARCODE_TYPE.1D").trim() + ` (Code 39)`; + case "CODE_39_MOD_43": + return this.translate.instant("BARCODE_TYPE.1D").trim() + ` (Code 39 mod 43)`; + case "CODE_93": + return this.translate.instant("BARCODE_TYPE.1D").trim() + ` (Code 93)`; + case "CODE_128": + return this.translate.instant("BARCODE_TYPE.1D").trim() + ` (Code 128)`; + case "CODABAR": + return this.translate.instant("BARCODE_TYPE.1D").trim() + ` (Codabar)`; + case "ITF": + return this.translate.instant("BARCODE_TYPE.1D").trim() + ` (ITF)`; + case "ITF_14": + return this.translate.instant("BARCODE_TYPE.1D").trim() + ` (ITF-14)`; + case "AZTEC": + return this.translate.instant("BARCODE_TYPE.AZTEC").trim(); + case "DATA_MATRIX": + return this.translate.instant("BARCODE_TYPE.DATA_MATRIX").trim(); + case "MAXICODE": + return this.translate.instant("BARCODE_TYPE.MAXICODE").trim(); + case "PDF_417": + return this.translate.instant("BARCODE_TYPE.PDF_417").trim(); + case "QR_CODE": + return this.translate.instant("BARCODE_TYPE.QR_CODE").trim(); + case "RSS_14": + return this.translate.instant("BARCODE_TYPE.RSS").trim(); + case "RSS_EXPANDED": + return this.translate.instant("BARCODE_TYPE.RSS").trim(); + default: + return this.env.resultContentFormat; + } + } + + async viewRecord(data: string, source: "view-log" | "view-bookmark"): Promise { + this.isLoading = true; + this.changeDetectorRef.detach(); + this.env.viewingScanRecords = []; + this.env.viewingBookmarks = []; + this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.reattach(); const loading = await this.presentLoading(this.translate.instant('PLEASE_WAIT')); - this.env.result = scannedData; - this.env.resultFormat = ""; - this.router.navigate(['tabs/result', { from: 'history', t: new Date().getTime() }], { state: { page: 'generate'}}).then( + this.env.resultContent = data; + this.env.resultContentFormat = ""; + this.env.recordSource = "view"; + this.env.detailedRecordSource = source; + this.env.viewResultFrom = "/tabs/history"; + this.router.navigate(['tabs/result']).then( () => { loading.dismiss(); } ); } - segmentChanged(ev: any) { } + async segmentChanged(ev: any) { + this.firstLoadItems(); + } - async addFavourite(record: ScanRecord, slidingItem: IonItemSliding) { - slidingItem.close(); - const flag = await this.env.saveBookmark(record.text); - await this.loadItems(); - if (flag === true) { - await this.presentToast(this.translate.instant("MSG.BOOKMARKED"), "short", "bottom"); - } else { + async addBookmark(record: ScanRecord, slidingItem: IonItemSliding) { + await slidingItem.close(); + if (this.env.bookmarks.find(x => x.text === record.text)) { await this.presentToast(this.translate.instant("MSG.ALREADY_BOOKMARKED"), "short", "bottom"); + return; } + await this.showBookmarkAlert(record); + } + + async showBookmarkAlert(record: ScanRecord) { + const alert = await this.alertController.create( + { + header: this.translate.instant('BOOKMARK'), + message: this.translate.instant('MSG.INPUT_TAG'), + cssClass: ['alert-bg'], + inputs: [ + { + name: 'tag', + id: 'tag', + type: 'text', + label: `${this.translate.instant("MSG.TAG_MAX_LENGTH")}`, + placeholder: `${this.translate.instant("MSG.TAG_MAX_LENGTH")}`, + max: 30 + } + ], + buttons: [ + { + text: this.translate.instant('CREATE'), + handler: async data => { + alert.dismiss(); + if (data.tag != null && data.tag.trim().length > 30) { + this.presentToast(this.translate.instant("MSG.TAG_MAX_LENGTH_EXPLAIN"), "short", "bottom"); + return true; + } + const bookmark = await this.env.saveBookmark(record.text, data.tag); + this.env.viewingBookmarks.unshift(bookmark); + this.env.viewingBookmarks.sort((a, b) => { + return ('' + a.tag ?? '').localeCompare(b.tag ?? ''); + }); + if (bookmark != null) { + await this.presentToast(this.translate.instant("MSG.BOOKMARKED"), "short", "bottom"); + } else { + await this.presentToast(this.translate.instant("MSG.ALREADY_BOOKMARKED"), "short", "bottom"); + } + } + } + ] + } + ) + await alert.present(); } async removeBookmark(bookmark: Bookmark, slidingItem: IonItemSliding) { - slidingItem.close(); + slidingItem.disabled = true; if (this.deleteToast) { await this.deleteToast.dismiss(); this.deleteToast = null; } await this.env.deleteBookmark(bookmark.text); - await this.loadItems(); + const index = this.env.viewingBookmarks.findIndex(x => x.text == bookmark.text); + if (index != -1) { + this.env.viewingBookmarks.splice(index, 1); + if (this.env.bookmarks?.length > this.env.viewingBookmarks.length) { + const bookmarks = [...this.env.bookmarks] + this.env.viewingBookmarks.push(...bookmarks.slice(this.env.viewingBookmarks.length, this.env.viewingBookmarks.length + 1)); + } + } this.deleteToast = await this.toastController.create({ message: this.translate.instant('MSG.UNDO_DELETE'), duration: 2000, - mode: "ios", color: "light", position: "top", buttons: [ @@ -126,7 +310,7 @@ export class HistoryPage { side: 'end', handler: async () => { await this.env.undoBookmarkDeletion(bookmark); - await this.loadItems(); + this.env.viewingBookmarks.splice(index, 0, bookmark); this.deleteToast.dismiss(); } } @@ -135,18 +319,75 @@ export class HistoryPage { await this.deleteToast.present(); } + async editBookmark(bookmark: Bookmark, slidingItem: IonItemSliding) { + await slidingItem.close(); + await this.showEditBookmarkAlert(bookmark); + } + + async showEditBookmarkAlert(bookmark: Bookmark) { + const alert = await this.alertController.create( + { + header: this.translate.instant('BOOKMARK'), + message: this.translate.instant('MSG.INPUT_TAG'), + cssClass: ['alert-bg'], + inputs: [ + { + name: 'tag', + id: 'tag', + type: 'text', + label: `${this.translate.instant("MSG.TAG_MAX_LENGTH")}`, + placeholder: `${this.translate.instant("MSG.TAG_MAX_LENGTH")}`, + value: bookmark.tag ?? '', + max: 30 + } + ], + buttons: [ + { + text: this.translate.instant('EDIT'), + handler: async data => { + alert.dismiss(); + if (data.tag != null && data.tag.trim().length > 30) { + this.presentToast(this.translate.instant("MSG.TAG_MAX_LENGTH_EXPLAIN"), "short", "bottom"); + return true; + } + this.isLoading = true; + await this.env.deleteBookmark(bookmark.text); + const index = this.env.viewingBookmarks.findIndex(x => x.text === bookmark.text); + if (index != -1) { + this.env.viewingBookmarks.splice(index, 1); + } + const newBookmark = await this.env.saveBookmark(bookmark.text, data.tag); + this.env.viewingBookmarks.unshift(newBookmark); + this.env.viewingBookmarks.sort((a, b) => { + return ('' + a.tag ?? '').localeCompare(b.tag ?? ''); + }); + this.isLoading = false; + } + } + ] + } + ) + await alert.present(); + } + async removeRecord(record: ScanRecord, slidingItem: IonItemSliding) { - slidingItem.close(); + slidingItem.disabled = true; if (this.deleteToast) { await this.deleteToast.dismiss(); this.deleteToast = null; } await this.env.deleteScanRecord(record.id); - await this.loadItems(); + const index = this.env.viewingScanRecords.findIndex(x => x.id == record.id); + if (index != -1) { + this.env.viewingScanRecords.splice(index, 1); + if (this.env.scanRecords?.length > this.env.viewingScanRecords.length) { + const scanRecords = [...this.env.scanRecords] + this.env.viewingScanRecords.push(...scanRecords.slice(this.env.viewingScanRecords.length, this.env.viewingScanRecords.length + 1)); + } + } this.deleteToast = await this.toastController.create({ message: this.translate.instant('MSG.UNDO_DELETE'), duration: 2000, - mode: "ios", color: "light", position: "top", buttons: [ @@ -155,7 +396,7 @@ export class HistoryPage { side: 'end', handler: async () => { await this.env.undoScanRecordDeletion(record); - await this.loadItems(); + this.env.viewingScanRecords.splice(index, 0, record); this.deleteToast.dismiss(); } } @@ -176,7 +417,9 @@ export class HistoryPage { text: this.translate.instant('YES'), handler: async () => { await this.env.deleteAllScanRecords(); - await this.loadItems(); + this.isLoading = true; + this.env.viewingScanRecords = []; + this.isLoading = false; } }, { @@ -196,7 +439,9 @@ export class HistoryPage { text: this.translate.instant('YES'), handler: async () => { await this.env.deleteAllBookmarks(); - await this.loadItems(); + this.isLoading = true; + this.env.viewingBookmarks = []; + this.isLoading = false; } }, { @@ -210,9 +455,19 @@ export class HistoryPage { } goSetting() { + this.isLoading = true; + this.changeDetectorRef.detach(); + this.env.viewingScanRecords = []; + this.env.viewingBookmarks = []; + this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.reattach(); this.router.navigate(['setting-record']); } + get denominator() { + return this.env.recordsLimit; + } + async presentAlert(msg: string, head: string, buttonText: string, buttonless: boolean = false): Promise { let alert: any; if (!buttonless) { @@ -237,8 +492,7 @@ export class HistoryPage { async presentLoading(msg: string): Promise { const loading = await this.loadingController.create({ - message: msg, - mode: "ios" + message: msg }); await loading.present(); return loading; @@ -254,7 +508,12 @@ export class HistoryPage { async tapHaptic() { if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { - await Haptics.impact({ style: ImpactStyle.Medium }); + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) } } } diff --git a/src/app/pages/import-image/import-image.module.ts b/src/app/pages/import-image/import-image.module.ts index acde60e..181be4d 100644 --- a/src/app/pages/import-image/import-image.module.ts +++ b/src/app/pages/import-image/import-image.module.ts @@ -9,7 +9,6 @@ import { ImportImagePageRoutingModule } from './import-image-routing.module'; import { ImportImagePage } from './import-image.page'; import { HttpClient } from '@angular/common/http'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; -import { MatButtonModule } from '@angular/material/button'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { @@ -29,7 +28,6 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { } }), ImportImagePageRoutingModule, - MatButtonModule, ], declarations: [ImportImagePage] }) diff --git a/src/app/pages/import-image/import-image.page.html b/src/app/pages/import-image/import-image.page.html index fb5fce6..cfc2f23 100644 --- a/src/app/pages/import-image/import-image.page.html +++ b/src/app/pages/import-image/import-image.page.html @@ -1,21 +1,52 @@ - - - {{ 'IMPORT_IMAGE' | translate }} - + +
+
+ + + {{ 'SIMPLE_QR' | translate}} + + + + + {{ 'IMPORT_IMAGE' | translate }} + +
-
+
- + -

- - {{ 'MSG.SCAN_QR_FROM_IMAGE' | translate }} - -

- +

+ + {{ 'MSG.SCAN_QR_FROM_IMAGE' | translate }} + +

+ + + + + {{ 'MSG.SCAN_QR_FROM_IMAGE_R1' | translate }} + + + + + + + {{ 'MSG.SCAN_QR_FROM_IMAGE_R2' | translate }} + + + + + + + {{ 'MSG.SCAN_QR_FROM_IMAGE_R3' | translate }} + + + {{ 'IMPORT_IMAGE' | translate }}
diff --git a/src/app/pages/import-image/import-image.page.ts b/src/app/pages/import-image/import-image.page.ts index 2bf608a..123e93a 100644 --- a/src/app/pages/import-image/import-image.page.ts +++ b/src/app/pages/import-image/import-image.page.ts @@ -8,6 +8,7 @@ import { Router } from '@angular/router'; import { Haptics, ImpactStyle } from '@capacitor/haptics'; import { Toast } from '@capacitor/toast'; import { BarcodeScanner } from '@capacitor-community/barcode-scanner'; +import { SplashScreen } from '@capacitor/splash-screen'; @Component({ selector: 'app-import-image', @@ -25,41 +26,54 @@ export class ImportImagePage { private router: Router, ) { } + async ionViewDidEnter() { + await SplashScreen.hide() + } + async importImage() { const getPictureLoading = await this.presentLoading(this.translate.instant('PLEASE_WAIT')); const options = { - quality: 100, - allowEditing: false, + quality: 50, + allowEditing: true, resultType: CameraResultType.DataUrl, source: CameraSource.Photos } as ImageOptions; - await Camera.getPhoto(options).then( - async (photo: Photo) => { - getPictureLoading.dismiss(); - const decodingLoading = await this.presentLoading(this.translate.instant('DECODING')); - await this.convertDataUrlToImageData(photo?.dataUrl ?? '').then( - async imageData => { - await this.getJsQr(imageData.imageData.data, imageData.width, imageData.height).then( - async qrValue => { - decodingLoading.dismiss(); - const loading = await this.presentLoading(this.translate.instant('PLEASE_WAIT')); - await this.processQrCode(qrValue, loading); - }, - async err => { - decodingLoading.dismiss(); - await this.presentToast(this.translate.instant("MSG.NO_QR_CODE"), "short", "bottom"); + await Camera.requestPermissions({ permissions: ['photos'] }).then( + async permissionResult => { + if (permissionResult.photos === 'granted' || permissionResult.photos === 'limited') { + await Camera.getPhoto(options).then( + async (photo: Photo) => { + getPictureLoading.dismiss(); + const decodingLoading = await this.presentLoading(this.translate.instant('DECODING')); + await this.convertDataUrlToImageData(photo?.dataUrl ?? '').then( + async imageData => { + await this.getJsQr(imageData.imageData.data, imageData.width, imageData.height).then( + async qrValue => { + decodingLoading.dismiss(); + const loading = await this.presentLoading(this.translate.instant('PLEASE_WAIT')); + await this.processQrCode(qrValue, loading); + }, + async _ => { + decodingLoading.dismiss(); + await this.presentToast(this.translate.instant("MSG.NO_QR_CODE"), "short", "center"); + } + ) + }, + async _ => { + decodingLoading.dismiss(); + await this.presentToast(this.translate.instant("MSG.NO_QR_CODE"), "short", "center"); + } + ); + }, + async err => { + getPictureLoading.dismiss(); + if (this.env.isDebugging) { + this.presentToast("Error when call Camera.getPhoto: " + JSON.stringify(err), "long", "top"); } - ) - }, - async err => { - decodingLoading.dismiss(); - await this.presentToast(this.translate.instant("MSG.NO_QR_CODE"), "short", "bottom"); - } - ); - }, - async err => { - getPictureLoading.dismiss(); - if (err?.message != null && err?.message == 'User denied access to photos') { + } + ); + } else { + getPictureLoading.dismiss(); const alert = await this.alertController.create({ header: this.translate.instant("PERMISSION_REQUIRED"), message: this.translate.instant("MSG.READ_IMAGE_PERMISSION"), @@ -72,7 +86,7 @@ export class ImportImagePage { } }, { - text: this.translate.instant("OK"), + text: this.translate.instant("CLOSE"), handler: () => { return true; } @@ -82,14 +96,25 @@ export class ImportImagePage { }); await alert.present(); } + }, + async err => { + getPictureLoading.dismiss(); + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Camera.requestPermissions: ' + JSON.stringify(err), position: "bottom", duration: "long" }) + } else { + Toast.show({ text: 'Unknown Error', position: "bottom", duration: "short" }) + } } ); } async processQrCode(scannedData: string, loading: HTMLIonLoadingElement): Promise { - this.env.result = scannedData; - this.env.resultFormat = "QR_CODE"; - this.router.navigate(['tabs/result', { from: 'import-image', t: new Date().getTime() }]).then( + this.env.resultContent = scannedData; + this.env.resultContentFormat = "QR_CODE"; + this.env.recordSource = "scan"; + this.env.detailedRecordSource = "scan-image"; + this.env.viewResultFrom = "/tabs/import-image"; + this.router.navigate(['tabs/result']).then( () => { loading.dismiss(); } @@ -120,8 +145,7 @@ export class ImportImagePage { async presentLoading(msg: string): Promise { const loading = await this.loadingController.create({ - message: msg, - mode: "ios" + message: msg }); await loading.present(); return loading; @@ -144,8 +168,20 @@ export class ImportImagePage { image.addEventListener('load', function () { canvas.width = image.width; canvas.height = image.height; + context.fillStyle = "white"; + context.fillRect(0, 0, canvas.width, canvas.height); context.drawImage(image, 0, 0, canvas.width, canvas.height); - resolve({ imageData: context.getImageData(0, 0, canvas.width, canvas.height), width: image.width, height: image.height }); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const avg = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3; + imageData.data[i] = avg; + imageData.data[i + 1] = avg; + imageData.data[i + 2] = avg; + } + const width = image.width; + const height = image.height; + resolve({ imageData: imageData, width: width, height: height }); }, false); if (uri.startsWith("data")) { image.src = uri; @@ -157,7 +193,7 @@ export class ImportImagePage { private async getJsQr(imageData: Uint8ClampedArray, width: number, height: number): Promise { return await new Promise((resolve, reject) => { - const qrcode = jsQR(imageData, width, height, { inversionAttempts: "dontInvert" }); + const qrcode = jsQR(imageData, width, height, { inversionAttempts: "attemptBoth" }); if (qrcode) { return resolve(qrcode.data); } else { @@ -168,7 +204,12 @@ export class ImportImagePage { async tapHaptic() { if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { - await Haptics.impact({ style: ImpactStyle.Medium }); + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) } } } diff --git a/src/app/pages/landing/landing-routing.module.ts b/src/app/pages/landing/landing-routing.module.ts new file mode 100644 index 0000000..eb02d78 --- /dev/null +++ b/src/app/pages/landing/landing-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { LandingPage } from './landing.page'; + +const routes: Routes = [ + { + path: '', + component: LandingPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class LandingPageRoutingModule {} diff --git a/src/app/pages/landing/landing.module.ts b/src/app/pages/landing/landing.module.ts new file mode 100644 index 0000000..36f8a14 --- /dev/null +++ b/src/app/pages/landing/landing.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { LandingPageRoutingModule } from './landing-routing.module'; + +import { LandingPage } from './landing.page'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClient } from '@angular/common/http'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + LandingPageRoutingModule + ], + declarations: [LandingPage] +}) +export class LandingPageModule {} diff --git a/src/app/pages/landing/landing.page.html b/src/app/pages/landing/landing.page.html new file mode 100644 index 0000000..b37b32c --- /dev/null +++ b/src/app/pages/landing/landing.page.html @@ -0,0 +1,9 @@ + + + {{ 'SIMPLE_QR' | translate}} + + + + + + \ No newline at end of file diff --git a/src/app/components/menu/menu.component.scss b/src/app/pages/landing/landing.page.scss similarity index 100% rename from src/app/components/menu/menu.component.scss rename to src/app/pages/landing/landing.page.scss diff --git a/src/app/pages/landing/landing.page.ts b/src/app/pages/landing/landing.page.ts new file mode 100644 index 0000000..8742333 --- /dev/null +++ b/src/app/pages/landing/landing.page.ts @@ -0,0 +1,95 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { Preferences } from '@capacitor/preferences'; +import { AlertController, IonRouterOutlet, Platform } from '@ionic/angular'; +import { TranslateService } from '@ngx-translate/core'; +import { EnvService } from 'src/app/services/env.service'; + +@Component({ + selector: 'app-landing', + templateUrl: './landing.page.html', + styleUrls: ['./landing.page.scss'], +}) +export class LandingPage { + + constructor( + private translate: TranslateService, + private platform: Platform, + private routerOutlet: IonRouterOutlet, + private router: Router, + private env: EnvService, + private alertController: AlertController, + ) { + if (this.platform.is('android')) { + this.platform.backButton.subscribeWithPriority(-1, async () => { + if (!this.routerOutlet.canGoBack()) { + if (this.router.url?.startsWith("/tabs/result")) { + if (this.env.viewResultFrom != null) { + const from = this.env.viewResultFrom; + this.router.navigate([`${from}`], { replaceUrl: true }); + } + } else { + if (this.router.url?.startsWith("/tabs")) { + if (this.router.url != this.env.startPage) { + this.router.navigate([this.env.startPage], { replaceUrl: true }); + } else { + await this.confirmExitApp(); + } + } + } + } + }); + } + } + + openGooglePlay(): void { + window.open(this.env.GOOGLE_PLAY_URL, '_system'); + } + + openAppStore(): void { + window.open(this.env.APP_STORE_URL, '_system'); + } + + async confirmExitApp(): Promise { + if (this.env.showExitAppAlert == "on") { + const alert = await this.alertController.create({ + header: this.translate.instant('EXIT_APP'), + message: this.translate.instant('MSG.EXIT_APP'), + inputs: [ + { + type: "checkbox", + label: this.translate.instant("MSG.TUTORIAL_NOT_SHOW_AGAIN"), + checked: false, + handler: async (result) => { + if (result.checked) { + this.env.showExitAppAlert = "off"; + } else { + this.env.showExitAppAlert = "on"; + } + await Preferences.set({ key: this.env.KEY_SHOW_EXIT_APP_ALERT, value: this.env.showExitAppAlert }); + } + } + ], + cssClass: ['alert-bg', 'alert-input-no-border'], + buttons: [ + { + text: this.translate.instant('EXIT'), + handler: () => { + navigator['app'].exitApp(); + } + }, + { + text: this.translate.instant('RATE_THE_APP'), + handler: () => { + this.openGooglePlay(); + } + } + ] + }); + await alert.present(); + } else { + navigator['app'].exitApp(); + } + } + +} diff --git a/src/app/pages/result/result.module.ts b/src/app/pages/result/result.module.ts index cfb863f..b60993a 100644 --- a/src/app/pages/result/result.module.ts +++ b/src/app/pages/result/result.module.ts @@ -7,12 +7,12 @@ import { IonicModule } from '@ionic/angular'; import { ResultPageRoutingModule } from './result-routing.module'; import { ResultPage } from './result.page'; -import { NgxQRCodeModule } from '@techiediaries/ngx-qrcode'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { HttpClient } from '@angular/common/http'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; +import { MatIconModule } from '@angular/material/icon'; export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); @@ -25,6 +25,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { IonicModule, MatFormFieldModule, MatInputModule, + MatIconModule, TranslateModule.forChild({ loader: { provide: TranslateLoader, diff --git a/src/app/pages/result/result.page.html b/src/app/pages/result/result.page.html index 262b821..da2ff87 100644 --- a/src/app/pages/result/result.page.html +++ b/src/app/pages/result/result.page.html @@ -1,6 +1,11 @@ - - + + {{ 'RESULT' | translate }} + + + {{ 'SETTINGS' | translate }} + + @@ -9,8 +14,20 @@
+ + + +
+ + + {{ contentTypeText }} +
+
+
+
+ + [ngTemplateOutletContext]="{ label: barcodeFormat + ('CONTENT' | translate), content: qrCodeContent, hint: env.resultContentFormat, showEdit: true }"> - - - - - @@ -73,7 +93,16 @@ + [ngTemplateOutletContext]="{ label: 'HIDDEN_NETWORK_?' | translate, content: wifiHidden === true? ('YES' | translate) : ('NO' | translate) }"> + + + + + +
@@ -81,95 +110,257 @@ - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - + + + + + - - - - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + +
+ + + {{ 'SAVE' | translate }} + + + + Facts + + + + {{ 'BROWSE' | translate}} + + + + {{ 'OPEN' | translate}} + + + + {{ 'ADD' | translate}} + + + + {{ 'CALL' | translate}} + + + + {{ 'SEND' | translate}} + + + + {{ 'SEND' | translate}} + + + + + + + + + + + + + + + + + {{ 'SEARCH' | translate}} + + + + {{ 'COPY' | translate}} + + + + {{ 'BASE64' | translate}} + + + + {{ 'SHOW' | translate}} + + + + {{ 'BOOKMARK' | translate}} + {{ 'BOOKMARKED' | translate}} + +
+
+
- - - - - - - - - - - - - - - - - - - - - - - - -
- - + + {{ label }} + [cdkAutosizeMaxRows]="20" readonly> + {{ hint }} + diff --git a/src/app/pages/result/result.page.scss b/src/app/pages/result/result.page.scss index 3ef16dd..078679c 100644 --- a/src/app/pages/result/result.page.scss +++ b/src/app/pages/result/result.page.scss @@ -27,4 +27,13 @@ ion-textarea[readonly] { max-width: 150px; max-height: 150px; } +} + +.detailed-action-button-container { + flex: 1; + overflow-x: scroll; +} + +.detailed-action-button-container::before, .detailed-action-button-container::after { + content: '' } \ No newline at end of file diff --git a/src/app/pages/result/result.page.ts b/src/app/pages/result/result.page.ts index 1e30f97..817a40e 100644 --- a/src/app/pages/result/result.page.ts +++ b/src/app/pages/result/result.page.ts @@ -1,49 +1,33 @@ -import { Component, OnInit, QueryList, ViewChildren } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Component, QueryList, ViewChildren } from '@angular/core'; import { Clipboard } from '@capacitor/clipboard'; -import { Contacts, ContactType, EmailAddress, NewContact, PhoneNumber } from '@capacitor-community/contacts' +import { ContactInput, Contacts, EmailInput, EmailType, PhoneInput, PhoneType } from '@capacitor-community/contacts'; import { SMS } from '@awesome-cordova-plugins/sms/ngx'; import { Haptics, ImpactStyle } from '@capacitor/haptics'; -import { AlertController, LoadingController, ModalController, Platform, PopoverController, ToastController } from '@ionic/angular'; +import { AlertController, LoadingController, ModalController, Platform } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; -import { NgxQrcodeElementTypes, NgxQrcodeErrorCorrectionLevels } from '@techiediaries/ngx-qrcode'; import { VCardContact } from 'src/app/models/v-card-contact'; -import { EnvService } from 'src/app/services/env.service'; -import { QrcodeComponent } from 'src/app/components/qrcode/qrcode.component'; +import { EnvService, QrResultContentTypeType } from 'src/app/services/env.service'; import { Toast } from '@capacitor/toast'; -import { animate, style, transition, trigger } from '@angular/animations'; import { MatFormField } from '@angular/material/form-field'; import { BarcodeScanner } from '@capacitor-community/barcode-scanner'; +import { QrCodePage } from 'src/app/modals/qr-code/qr-code.page'; +import { fadeIn } from 'src/app/utils/animations'; +import { Router } from '@angular/router'; +import { QRCodeElementType } from 'angularx-qrcode'; @Component({ selector: 'app-result', templateUrl: './result.page.html', styleUrls: ['./result.page.scss'], - animations: [ - trigger( - 'inOutAnimation', - [ - transition( - ':enter', - [ - style({ opacity: 0 }), - animate( - '1s ease', - style({ opacity: 1 }) - ) - ] - ) - ] - ) - ] + animations: [fadeIn] }) -export class ResultPage implements OnInit { +export class ResultPage { - contentType: "freeText" | "url" | "contact" | "phone" | "sms" | "email" | "wifi" = "freeText"; + contentType: QrResultContentTypeType = "freeText"; qrCodeContent: string; - qrElementType: NgxQrcodeElementTypes = NgxQrcodeElementTypes.CANVAS; - errorCorrectionLevel: NgxQrcodeErrorCorrectionLevels = NgxQrcodeErrorCorrectionLevels.LOW; + qrElementType: QRCodeElementType = "canvas"; + errorCorrectionLevel: 'low' | 'medium' | 'quartile' | 'high' | 'L' | 'M' | 'Q' | 'H' = 'low'; qrMargin: number = 3; phoneNumber: string; @@ -61,6 +45,9 @@ export class ResultPage implements OnInit { wifiEncryption: 'NONE' | 'WEP' | 'WPA'; wifiHidden: boolean = false; + latitude: number; + longitude: number; + base64Encoded: boolean = false; base64EncodedText: string = ""; base64Decoded: boolean = false; @@ -70,76 +57,115 @@ export class ResultPage implements OnInit { showQrFirst: boolean = false; + resultSaved: boolean = false; + @ViewChildren(MatFormField) formFields: QueryList; constructor( private platform: Platform, public alertController: AlertController, public loadingController: LoadingController, - private route: ActivatedRoute, - private router: Router, public env: EnvService, public modalController: ModalController, private sms: SMS, public translate: TranslateService, - private popoverController: PopoverController, - ) { - if (this.router.getCurrentNavigation().extras.state) { - const state = this.router.getCurrentNavigation().extras.state; - if (state.page == 'generate') { + private router: Router, + ) { } + + ionViewWillEnter() { + if (this.env.detailedRecordSource != null) { + if (this.env.detailedRecordSource == 'create' && this.env.showQrAfterCreate == 'on') { + this.showQrFirst = true; + } else if (this.env.detailedRecordSource == 'view-log' && this.env.showQrAfterLogView == 'on') { + this.showQrFirst = true; + } else if (this.env.detailedRecordSource == 'view-bookmark' && this.env.showQrAfterBookmarkView == 'on') { + this.showQrFirst = true; + } else if (this.env.detailedRecordSource == 'scan-camera' && this.env.showQrAfterCameraScan == 'on') { + this.showQrFirst = true; + } else if (this.env.detailedRecordSource == 'scan-image' && this.env.showQrAfterImageScan == 'on') { this.showQrFirst = true; } } + this.qrCodeContent = this.env.resultContent; + this.setContentType(); } - async ngOnInit() { - this.qrCodeContent = this.env.result; - this.setContentType(); - if (this.env.scanRecordLogging === 'on') { + async ionViewDidEnter(): Promise { + if (this.contentType == 'url' && this.env.autoOpenUrl == 'on' && this.env.recordSource == 'scan') { + setTimeout(() => { + this.presentToast(this.translate.instant("AUTO_OPEN_URL"), "short", "bottom"); + if (this.isHttp) { + this.browseWebsite(); + } else { + this.openLink(); + } + }, 300); + } else if (this.showQrFirst) { + this.showQrFirst = false; + if (this.qrCodeContent && this.qrCodeContent.trim().length > 0) { + await this.enlarge(); + } + } + if (this.env.scanRecordLogging == 'on' && this.qrCodeContent != null && this.qrCodeContent != "") { await this.env.saveScanRecord(this.qrCodeContent); } - if (this.env.bookmarks.find(x => x.text === this.qrCodeContent)) { + if (this.env.bookmarks.find(x => x.text == this.qrCodeContent)) { this.bookmarked = true; } } - async ionViewDidEnter(): Promise { - if (this.env.vibration === 'on' || this.env.vibration === 'on-scanned') { - setTimeout( - async () => { - await Haptics.vibrate(); - }, 200 - ); - } - if (this.showQrFirst) { - this.showQrFirst = false; - if (this.qrCodeContent && this.qrCodeContent.trim().length > 0) { - setTimeout( - async () => await this.enlarge(), 100 - ); - ; - } - } + ionViewDidLeave() { + this.reset(); } - async ionViewWillLeave(): Promise { - this.base64Decoded = false; + async saveRecord() { + if (this.qrCodeContent != null && this.qrCodeContent != "") { + await this.env.saveScanRecord(this.qrCodeContent); + } + this.resultSaved = true; + this.presentToast(this.translate.instant("SAVED"), "short", "bottom"); + } + + reset() { + this.contentType = "freeText"; + delete this.qrCodeContent; + delete this.phoneNumber + delete this.vCardContact + delete this.smsContent + delete this.toEmails + delete this.ccEmails + delete this.bccEmails + delete this.emailSubject + delete this.emailBody + delete this.wifiSSID + delete this.wifiPassword + delete this.wifiEncryption + delete this.wifiHidden + delete this.latitude + delete this.longitude this.base64Encoded = false; + this.base64EncodedText = ""; + this.base64Decoded = false; + this.base64DecodedText = ""; + this.bookmarked = false; + this.showQrFirst = false; + this.resultSaved = false; + delete this.env.recordSource; + delete this.env.detailedRecordSource; + delete this.env.viewResultFrom; } setContentType(): void { - const urlPrefix1 = "HTTPS://"; - const urlPrefix2 = "HTTP://"; const contactPrefix = "BEGIN:VCARD"; const phonePrefix = "TEL:"; const smsPrefix = "SMSTO:"; - const emailPrefix = "MAILTO:"; + const emailW3CPrefix = "MAILTO:"; + const emailDoconoPrefix = "MATMSG:"; const wifiPrefix = "WIFI:"; + const geoPrefix = "GEO:"; const content0 = this.qrCodeContent.trim(); const tContent = this.qrCodeContent.trim().toUpperCase(); - if (tContent.substr(0, urlPrefix1.length) === urlPrefix1 || tContent.substr(0, urlPrefix2.length) === urlPrefix2) { - this.contentType = "url"; - } else if (tContent.substr(0, contactPrefix.length) === contactPrefix) { + if (tContent.substr(0, contactPrefix.length) === contactPrefix) { this.contentType = "contact"; this.generateVCardContact(); } else if (tContent.substr(0, phonePrefix.length) === phonePrefix) { @@ -154,17 +180,38 @@ export class ResultPage implements OnInit { } else { this.phoneNumber = tContent2.substr(0); } - } else if (tContent.substr(0, emailPrefix.length) === emailPrefix) { - this.contentType = "email"; - this.prepareEmail(); + } else if (tContent.substr(0, emailW3CPrefix.length) === emailW3CPrefix) { + this.contentType = "emailW3C"; + this.prepareMailToEmail(); + } else if (tContent.substr(0, emailDoconoPrefix.length) === emailDoconoPrefix) { + this.contentType = "emailDocomo"; + this.prepareMATMSGEmail(); } else if (tContent.substr(0, wifiPrefix.length) === wifiPrefix) { this.contentType = "wifi"; this.prepareWifi(); + } else if (tContent.substring(0, geoPrefix.length) === geoPrefix) { + this.contentType = "geo"; + this.latitude = +tContent.substring(geoPrefix.length, tContent.indexOf(",")); + this.longitude = +tContent.substring(tContent.indexOf(",") + 1); + } else if (this.isValidUrl(content0)) { + this.contentType = "url"; } else { this.contentType = "freeText"; } } + private isValidUrl(text: string): boolean { + let url: URL; + + try { + url = new URL(text); + } catch (_) { + return false; + } + + return url.protocol != null && url.protocol.length > 0; + } + get qrColorDark(): string { return "#222428"; } @@ -173,99 +220,163 @@ export class ResultPage implements OnInit { return "#ffffff"; } - browseWebsite(): void { - window.open(this.qrCodeContent, '_system'); + editContent() { + this.env.editingContent = true; + this.router.navigate(['tabs/generate'], { replaceUrl: true }); + } + + searchOpenFoodFacts() { + window.open(`https://world.openfoodfacts.org/product/${this.qrCodeContent}`, '_system', 'location=yes'); + } + + browseWebsite() { + window.open(this.qrCodeContent, '_system', 'location=yes'); + } + + async openLink(): Promise { + window.open(this.qrCodeContent); + } + + get isHttp(): boolean { + const urlPrefix1 = "HTTPS://"; + const urlPrefix2 = "HTTP://"; + const tContent = this.qrCodeContent.trim().toUpperCase(); + if (tContent.substring(0, urlPrefix1.length) === urlPrefix1 || tContent.substring(0, urlPrefix2.length) === urlPrefix2) { + return true; + } + return false; } async addContact(): Promise { - let newContact = null + let contactInput: ContactInput = {}; if (this.contentType === "contact") { - const phoneNumbers = []; + const phoneNumbers: PhoneInput[] = []; if (this.vCardContact?.defaultPhoneNumber != null) { - const phoneNumber = { number: this.vCardContact?.defaultPhoneNumber } as PhoneNumber; + const phoneNumber: PhoneInput = { + type: PhoneType.Mobile, + label: 'mobile', + number: this.vCardContact?.defaultPhoneNumber, + isPrimary: true, + }; phoneNumbers.push(phoneNumber); } if (this.vCardContact?.homePhoneNumber != null) { - const phoneNumber = { number: this.vCardContact?.homePhoneNumber } as PhoneNumber; + const phoneNumber: PhoneInput = { + type: PhoneType.Home, + label: 'home', + number: this.vCardContact?.homePhoneNumber, + }; phoneNumbers.push(phoneNumber); } if (this.vCardContact?.workPhoneNumber != null) { - const phoneNumber = { number: this.vCardContact?.homePhoneNumber } as PhoneNumber; + const phoneNumber: PhoneInput = { + type: PhoneType.Work, + label: 'work', + number: this.vCardContact?.workPhoneNumber, + }; phoneNumbers.push(phoneNumber); } if (this.vCardContact?.mobilePhoneNumber != null) { - const phoneNumber = { number: this.vCardContact?.mobilePhoneNumber } as PhoneNumber; + const phoneNumber: PhoneInput = { + type: PhoneType.Mobile, + label: 'mobile', + number: this.vCardContact?.mobilePhoneNumber, + }; phoneNumbers.push(phoneNumber); } - const emails = []; + const emails: EmailInput[] = []; if (this.vCardContact?.defaultEmail != null) { - const address = { address: this.vCardContact?.defaultEmail } as EmailAddress; - emails.push(address); + const emailInput: EmailInput = { + type: EmailType.Home, + label: 'home', + isPrimary: true, + address: this.vCardContact?.defaultEmail, + }; + emails.push(emailInput); } if (this.vCardContact?.homeEmail != null) { - const address = { address: this.vCardContact?.homeEmail } as EmailAddress; - emails.push(address); + const emailInput: EmailInput = { + type: EmailType.Home, + label: 'home', + address: this.vCardContact?.homeEmail, + }; + emails.push(emailInput); } if (this.vCardContact?.workEmail != null) { - const address = { address: this.vCardContact?.workEmail } as EmailAddress; - emails.push(address); + const emailInput: EmailInput = { + type: EmailType.Work, + label: 'work', + address: this.vCardContact?.workEmail, + }; + emails.push(emailInput); } - newContact = { - contactType: ContactType.Person, - givenName: this.vCardContact?.givenName ?? this.vCardContact?.fullName ?? '', - familyName: this.vCardContact?.familyName, - phoneNumbers: phoneNumbers, - emailAddresses: emails - } as NewContact; + contactInput.phones = phoneNumbers; + contactInput.emails = emails; + contactInput.name = { + given: this.vCardContact?.givenName ?? this.vCardContact?.fullName ?? '', + family: this.vCardContact?.familyName, + }; } else if (this.contentType === "sms" || this.contentType === "phone") { - const phoneNumbers = []; - const phoneNumber = { number: this.phoneNumber } as PhoneNumber; - phoneNumbers.push(phoneNumber); - newContact = { - contactType: ContactType.Person, - phoneNumbers: phoneNumbers - } as NewContact; - } - if (newContact != null) { - await Contacts.getPermissions().then( - async permission => { - if (permission.granted) { - await Contacts.saveContact(newContact).then( - _ => { - this.presentToast(this.translate.instant('MSG.SAVED_CONTACT'), "short", "bottom"); - } - ) - .catch( - err => { - this.presentToast(this.translate.instant('MSG.FAILED_SAVING_CONTACT'), "short", "bottom"); - } - ) - } else { - const alert = await this.alertController.create({ - header: this.translate.instant("PERMISSION_REQUIRED"), - message: this.translate.instant("MSG.CONTACT_PERMISSION"), - buttons: [ - { - text: this.translate.instant("SETTING"), - handler: () => { - BarcodeScanner.openAppSettings(); - return true; - } - }, - { - text: this.translate.instant("OK"), - handler: () => { - return true; - } - } - ], - cssClass: ['alert-bg'] - }); - await alert.present(); - } + const phones: PhoneInput[] = [ + { + type: PhoneType.Mobile, + label: 'mobile', + number: this.phoneNumber, + isPrimary: true, } - ); + ]; + contactInput.phones = phones; } + if (this.platform.is('ios')) { + // TODO: iOS contact handling + // await Contacts.checkPermissions().then( + // async permission => { + // if (permission.contacts == 'granted') { + // await this.saveContact(newContact); + // } else { + // const alert = await this.alertController.create({ + // header: this.translate.instant("PERMISSION_REQUIRED"), + // message: this.translate.instant("MSG.CONTACT_PERMISSION"), + // buttons: [ + // { + // text: this.translate.instant("SETTING"), + // handler: () => { + // BarcodeScanner.openAppSettings(); + // return true; + // } + // }, + // { + // text: this.translate.instant("CLOSE"), + // handler: () => { + // return true; + // } + // } + // ], + // cssClass: ['alert-bg'] + // }); + // await alert.present(); + // } + // } + // ); + } else { // Android doesn't need to get permission + await this.saveContact(contactInput); + } + } + + private async saveContact(contactInput: ContactInput) { + await Contacts.createContact({ contact: contactInput }).then( + _ => { + this.presentToast(this.translate.instant('MSG.SAVED_CONTACT'), "short", "bottom"); + } + ).catch( + err => { + if (this.env.isDebugging) { + this.presentToast("Error when call Contacts.createContact: " + JSON.stringify(err), "long", "top"); + } else { + this.presentToast(this.translate.instant('MSG.FAILED_SAVING_CONTACT'), "short", "bottom"); + } + } + ); } async callPhone(): Promise { @@ -314,13 +425,20 @@ export class ResultPage implements OnInit { } async sendEmail(): Promise { - window.open(this.qrCodeContent, "_system"); + if (this.contentType === 'emailW3C') { + window.open(this.qrCodeContent, "_system"); + } else if (this.contentType === 'emailDocomo') { + const content = `mailto:${this.toEmails}?subject=${encodeURIComponent(this.emailSubject)}&body=${encodeURIComponent(this.emailBody)}`; + window.open(content, "_system"); + } } async enlarge(): Promise { const modal = await this.modalController.create({ - component: QrcodeComponent, - cssClass: 'qrcode-modal', + component: QrCodePage, + breakpoints: [0, 0.5, 1], + initialBreakpoint: 0.5, + cssClass: 'fullscreen-modal', componentProps: { qrCodeContent: this.qrCodeContent } }); await modal.present(); @@ -341,6 +459,15 @@ export class ResultPage implements OnInit { case 'duckduckgo': searchUrl = this.env.DUCK_DUCK_GO_SEARCH_URL; break; + case 'yandex': + searchUrl = this.env.YANDEX_SEARCH_URL; + break; + case 'ecosia': + searchUrl = this.env.ECOSIA_SEARCH_URL; + break; + case 'brave': + searchUrl = this.env.BRAVE_SEARCH_URL; + break; default: searchUrl = this.env.GOOGLE_SEARCH_URL; break; @@ -393,7 +520,7 @@ export class ResultPage implements OnInit { alert.dismiss(); await Clipboard.write({ string: this.qrCodeContent }).then( async () => { - await this.presentToast(this.translate.instant('MSG.COPIED'), "short", "bottom"); + await this.presentToast(this.translate.instant('COPIED'), "short", "bottom"); } ) } @@ -404,7 +531,7 @@ export class ResultPage implements OnInit { alert.dismiss(); await Clipboard.write({ string: this.base64EncodedText }).then( async () => { - await this.presentToast(this.translate.instant('MSG.COPIED'), "short", "bottom"); + await this.presentToast(this.translate.instant('COPIED'), "short", "bottom"); } ) } @@ -415,7 +542,7 @@ export class ResultPage implements OnInit { alert.dismiss(); await Clipboard.write({ string: this.base64DecodedText }).then( async () => { - await this.presentToast(this.translate.instant('MSG.COPIED'), "short", "bottom"); + await this.presentToast(this.translate.instant('COPIED'), "short", "bottom"); } ) } @@ -437,7 +564,7 @@ export class ResultPage implements OnInit { alert.dismiss(); await Clipboard.write({ string: this.qrCodeContent }).then( async () => { - await this.presentToast(this.translate.instant('MSG.COPIED'), "short", "bottom"); + await this.presentToast(this.translate.instant('COPIED'), "short", "bottom"); } ) } @@ -448,7 +575,7 @@ export class ResultPage implements OnInit { alert.dismiss(); await Clipboard.write({ string: this.base64EncodedText }).then( async () => { - await this.presentToast(this.translate.instant('MSG.COPIED'), "short", "bottom"); + await this.presentToast(this.translate.instant('COPIED'), "short", "bottom"); } ) } @@ -470,7 +597,7 @@ export class ResultPage implements OnInit { alert.dismiss(); await Clipboard.write({ string: this.qrCodeContent }).then( async () => { - await this.presentToast(this.translate.instant('MSG.COPIED'), "short", "bottom"); + await this.presentToast(this.translate.instant('COPIED'), "short", "bottom"); } ) } @@ -481,7 +608,7 @@ export class ResultPage implements OnInit { alert.dismiss(); await Clipboard.write({ string: this.base64DecodedText }).then( async () => { - await this.presentToast(this.translate.instant('MSG.COPIED'), "short", "bottom"); + await this.presentToast(this.translate.instant('COPIED'), "short", "bottom"); } ) } @@ -493,7 +620,7 @@ export class ResultPage implements OnInit { } else { await Clipboard.write({ string: this.qrCodeContent }).then( async () => { - await this.presentToast(this.translate.instant('MSG.COPIED'), "short", "bottom"); + await this.presentToast(this.translate.instant('COPIED'), "short", "bottom"); } ) } @@ -522,7 +649,7 @@ export class ResultPage implements OnInit { } else if (!failEncoded && failDecoded) { await this.presentToast(this.translate.instant('MSG.NOT_BASE64_DE'), "short", "center"); } - setTimeout(() => this.formFields?.forEach(ff => ff.updateOutlineGap()), 100); + // setTimeout(() => this.formFields?.forEach(ff => ff.updateOutlineGap()), 100); } generateVCardContact(): void { @@ -557,7 +684,6 @@ export class ResultPage implements OnInit { lines.forEach( line => { const tLine = line.trim(); - console.log(tLine); if (tLine.toUpperCase().substr(0, fullNameId1.length) === fullNameId1) { this.vCardContact.fullName = tLine.substr(fullNameId1.length); } else if (tLine.toUpperCase().substr(0, fullNameId2.length) === fullNameId2) { @@ -619,7 +745,7 @@ export class ResultPage implements OnInit { ) } - prepareEmail(): void { + prepareMailToEmail(): void { const emailPrefix = "MAILTO:"; const emailString = this.qrCodeContent.substr(emailPrefix.length); const emailParts = emailString.split('?', 2); @@ -649,6 +775,30 @@ export class ResultPage implements OnInit { } } + prepareMATMSGEmail() { + const emailPrefix = "MATMSG:"; + const emailString = this.qrCodeContent.substr(emailPrefix.length); + const emailParts = emailString.split(";"); + if (emailParts.length > 0) { + const toPrefix = "TO:"; + const subjectPrefix = "SUB:"; + const bodyPrefix = "BODY:"; + emailParts.forEach( + (part) => { + if (part.toUpperCase().substr(0, toPrefix.length) === toPrefix) { + this.toEmails = part.substr(toPrefix.length); + } + if (part.toUpperCase().substr(0, subjectPrefix.length) === subjectPrefix) { + this.emailSubject = decodeURIComponent(part.substr(subjectPrefix.length)); + } + if (part.toUpperCase().substr(0, bodyPrefix.length) === bodyPrefix) { + this.emailBody = decodeURIComponent(part.substr(bodyPrefix.length)); + } + } + ); + } + } + prepareWifi(): void { const wifiPrefix = "WIFI:"; const wifiString = this.qrCodeContent.substr(wifiPrefix.length); @@ -678,26 +828,110 @@ export class ResultPage implements OnInit { } } - async addBookmark() { - const flag = await this.env.saveBookmark(this.qrCodeContent); - if (this.env.bookmarks.find(x => x.text === this.qrCodeContent)) { - this.bookmarked = true; + async handleBookmark() { + if (!this.bookmarked) { + await this.showBookmarkAlert(this.qrCodeContent); } else { - this.bookmarked = false; + await this.env.deleteBookmark(this.qrCodeContent); + if (this.env.bookmarks.find(x => x.text === this.qrCodeContent)) { + this.bookmarked = true; + } else { + this.bookmarked = false; + } } } - async removeBookmark() { - await this.env.deleteBookmark(this.qrCodeContent); - if (this.env.bookmarks.find(x => x.text === this.qrCodeContent)) { - this.bookmarked = true; - } else { - this.bookmarked = false; + async showBookmarkAlert(content: string) { + const alert = await this.alertController.create( + { + header: this.translate.instant('BOOKMARK'), + message: this.translate.instant('MSG.INPUT_TAG'), + cssClass: ['alert-bg'], + inputs: [ + { + name: 'tag', + id: 'tag', + type: 'text', + label: `${this.translate.instant("MSG.TAG_MAX_LENGTH")}`, + placeholder: `${this.translate.instant("MSG.TAG_MAX_LENGTH")}`, + max: 30 + } + ], + buttons: [ + { + text: this.translate.instant('CREATE'), + handler: async data => { + alert.dismiss(); + if (data.tag != null && data.tag.trim().length > 30) { + this.presentToast(this.translate.instant("MSG.TAG_MAX_LENGTH_EXPLAIN"), "short", "bottom"); + return true; + } + await this.env.saveBookmark(content, data.tag); + if (this.env.bookmarks.find(x => x.text === this.qrCodeContent)) { + this.bookmarked = true; + } else { + this.bookmarked = false; + } + } + } + ] + } + ) + await alert.present(); + } + + get contentTypeText(): string { + switch (this.contentType) { + case 'freeText': + return this.translate.instant("FREE_TEXT"); + case 'contact': + return this.translate.instant("VCARD_CONTACT"); + case 'emailW3C': + return this.translate.instant("EMAIL_W3C_STANDARD"); + case 'emailDocomo': + return this.translate.instant("EMAIL_NTT_DOCOMO"); + case 'geo': + return this.translate.instant("GEOLOCATION"); + case 'phone': + return this.translate.instant("PHONE_NO"); + case 'sms': + return this.translate.instant("MESSAGE"); + case 'url': + return this.translate.instant("URL"); + case 'wifi': + return this.translate.instant("WIFI"); + default: + return this.translate.instant("UNKNOWN"); + } + } + + get contentTypeIcon() { + switch (this.contentType) { + case "freeText": + return "format_align_left"; + case "url": + return "link"; + case "contact": + return "contact_phone"; + case 'geo': + return "location_on"; + case "phone": + return "call"; + case "sms": + return "sms"; + case "emailW3C": + return "email"; + case "emailDocomo": + return "email"; + case "wifi": + return "wifi"; + default: + return ""; } } get barcodeFormat(): string { - switch (this.env.resultFormat) { + switch (this.env.resultContentFormat) { case "UPC_A": return this.translate.instant("BARCODE_TYPE.UPC"); case "UPC_E": @@ -737,10 +971,36 @@ export class ResultPage implements OnInit { case "RSS_EXPANDED": return this.translate.instant("BARCODE_TYPE.RSS"); default: - return this.env.resultFormat; + return this.env.resultContentFormat; } } + get isValidEan(): boolean { + if (this.qrCodeContent == null) { + return false; + } + const isValidLength = this.qrCodeContent.length === 18 || this.qrCodeContent.length === 14 || this.qrCodeContent.length === 13 || this.qrCodeContent.length === 8 || this.qrCodeContent.length === 5; + return isValidLength && /^\d+$/.test(this.qrCodeContent) && this.testEanChecksum(this.qrCodeContent); + } + + private testEanChecksum(text: string): boolean { + const digits = text.slice(0, -1); + const checkDigit = parseInt(text.slice(-1)); + if (isNaN(checkDigit)) { + return false; + } + let sum = 0; + for (let i = digits.length - 1; i >= 0; i--) { + const digit = parseInt(digits.charAt(i)); + if (isNaN(digit)) { + return false; + } + sum += (digit * (1 + (2 * (i % 2)))) | 0; + } + sum = (10 - (sum % 10)) % 10; + return sum === checkDigit; + } + get finalContactName(): string { if (!this.vCardContact) { return ''; @@ -760,6 +1020,10 @@ export class ResultPage implements OnInit { return this.translate.instant("NOT_PROVIDED"); } + goSetting() { + this.router.navigate(['setting-result']); + } + get ngMatThemeClass() { switch (this.env.colorTheme) { case 'dark': @@ -785,21 +1049,9 @@ export class ResultPage implements OnInit { }); } - private base64toBlob(base64Data: string, contentType: string): Blob { - const byteString = atob(base64Data); - const ab = new ArrayBuffer(byteString.length); - const ia = new Uint8Array(ab); - for (let i = 0; i < byteString.length; i++) { - ia[i] = byteString.charCodeAt(i); - } - const blob = new Blob([ab], { type: contentType }); - return blob; - } - async presentLoading(msg: string): Promise { const loading = await this.loadingController.create({ - message: msg, - mode: "ios" + message: msg }); await loading.present(); return loading; @@ -807,7 +1059,12 @@ export class ResultPage implements OnInit { async tapHaptic() { if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { - await Haptics.impact({ style: ImpactStyle.Medium }); + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) } } } diff --git a/src/app/pages/scan/scan.page.html b/src/app/pages/scan/scan.page.html index b669cce..90a1c66 100644 --- a/src/app/pages/scan/scan.page.html +++ b/src/app/pages/scan/scan.page.html @@ -1,10 +1,19 @@ - - - {{ 'SIMPLE_QR' | translate}} - + +
+
+ + + {{ 'SIMPLE_QR' | translate}} + + + + + {{ 'SCAN' | translate }} + +
- +
diff --git a/src/app/pages/scan/scan.page.scss b/src/app/pages/scan/scan.page.scss index d440095..89c19f3 100644 --- a/src/app/pages/scan/scan.page.scss +++ b/src/app/pages/scan/scan.page.scss @@ -19,11 +19,3 @@ body { max-height: 50%; opacity: 0.7; } - -.master-fab-btn { - --background: var(--ion-color-primary); -} - -.master-fab-btn.fab-button-close-active { - --background: var(--ion-color-secondary); -} diff --git a/src/app/pages/scan/scan.page.ts b/src/app/pages/scan/scan.page.ts index 2a02482..24fa484 100644 --- a/src/app/pages/scan/scan.page.ts +++ b/src/app/pages/scan/scan.page.ts @@ -1,8 +1,8 @@ -import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; import { BarcodeScanner, ScanResult } from '@capacitor-community/barcode-scanner'; import { SplashScreen } from '@capacitor/splash-screen'; -import { AlertController, IonContent, IonRouterOutlet, LoadingController, ModalController, Platform, ToastController } from '@ionic/angular'; +import { AlertController, IonRouterOutlet, LoadingController, Platform } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; import { EnvService } from 'src/app/services/env.service'; import { Haptics, ImpactStyle } from '@capacitor/haptics'; @@ -20,6 +20,8 @@ enum CameraChoice { }) export class ScanPage { + @ViewChild('content') contentEl: HTMLIonContentElement; + cameraChoice: CameraChoice = CameraChoice.BACK; cameraActive: boolean = false; flashActive: boolean = false; @@ -31,40 +33,18 @@ export class ScanPage { public loadingController: LoadingController, public routerOutlet: IonRouterOutlet, private router: Router, - private env: EnvService, + public env: EnvService, public translate: TranslateService, - private toastController: ToastController, - private platform: Platform, - ) { - if (this.platform.is('android')) { - this.platform.backButton.subscribeWithPriority(-1, async () => { - if (!this.routerOutlet.canGoBack()) { - if (this.router.url?.startsWith("/tabs/result")) { - const urlSeg = this.router.url?.split(";") - if (urlSeg.length > 1) { - if (urlSeg[1].startsWith("from=")) { - const from = urlSeg[1].substring(5); - if (from.length > 0) { - this.router.navigate([`/tabs/${from}`], { replaceUrl: true }); - } - } - } - } else { - if (this.router.url?.startsWith("/tabs")) { - if (this.router.url != "/tabs/scan") { - this.router.navigate(['/tabs/scan'], { replaceUrl: true }); - } else { - await this.confirmExitApp(); - } - } - } - } - }); + ) { } + + ionViewWillEnter() { + if (this.contentEl != null) { + this.contentEl.color = "darker"; } } async ionViewDidEnter(): Promise { - await SplashScreen.hide(); + await SplashScreen.hide() await BarcodeScanner.disableTorch().then( _ => { this.flashActive = false; @@ -82,23 +62,6 @@ export class ScanPage { await this.stopScanner(); } - async loadPatchNote() { - await this.env.storageGet(this.env.PATCH_NOTE_STORAGE_KEY).then( - async value => { - if (value != null) { - this.env.notShowUpdateNotes = (value === 'yes' ? true : false); - } else { - this.env.notShowUpdateNotes = false; - } - await this.env.storageSet(this.env.PATCH_NOTE_STORAGE_KEY, 'yes'); - if (this.env.notShowUpdateNotes === false) { - this.env.notShowUpdateNotes = true; - await this.showUpdateNotes(); - } - } - ); - } - async stopScanner(): Promise { await BarcodeScanner.stopScan(); this.cameraActive = false; @@ -107,13 +70,12 @@ export class ScanPage { async prepareScanner(): Promise { const result = await BarcodeScanner.checkPermission({ force: true }); if (result.granted) { - await this.loadPatchNote(); await this.scanQr(); } else { this.permissionAlert?.dismiss(); this.permissionAlert = await this.alertController.create({ header: this.translate.instant("PERMISSION_REQUIRED"), - message: this.translate.instant("MSG.CAMERA_PERMISSION_1"), + message: this.translate.instant("MSG.CAMERA_PERMISSION"), buttons: [ { text: this.translate.instant("SETTING"), @@ -123,7 +85,7 @@ export class ScanPage { } }, { - text: this.translate.instant("OK"), + text: this.translate.instant("CLOSE"), handler: () => { return true; } @@ -140,21 +102,30 @@ export class ScanPage { await BarcodeScanner.hideBackground(); this.cameraActive = true; await BarcodeScanner.prepare(); + if (this.contentEl != null) { + this.contentEl.color = ""; + } await BarcodeScanner.startScan().then( async (result: ScanResult) => { if (result.hasContent) { - console.log(result.content); const text = result.content; - if (text === undefined || text === null || (text && text.trim().length <= 0) || text === "") { + if (text == null || text?.trim()?.length <= 0 || text == "") { this.presentToast(this.translate.instant('MSG.QR_CODE_VALUE_NOT_EMPTY'), "short", "center"); this.scanQr(); return; } + if (this.contentEl != null) { + this.contentEl.color = "darker"; + } if (this.env.vibration === 'on' || this.env.vibration === 'on-scanned') { - await Haptics.vibrate(); + await Haptics.vibrate({ duration: 100 }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) } const loading = await this.presentLoading(this.translate.instant('PLEASE_WAIT')); - await this.stopScanner(); await this.processQrCode(text, result.format, loading); } else { this.presentToast(this.translate.instant('MSG.QR_CODE_VALUE_NOT_EMPTY'), "short", "center"); @@ -166,9 +137,12 @@ export class ScanPage { } async processQrCode(scannedData: string, format: string, loading: HTMLIonLoadingElement): Promise { - this.env.result = scannedData; - this.env.resultFormat = format; - this.router.navigate(['tabs/result', { from: 'scan', t: new Date().getTime() }]).then( + this.env.resultContent = scannedData; + this.env.resultContentFormat = format; + this.env.recordSource = "scan"; + this.env.detailedRecordSource = "scan-camera"; + this.env.viewResultFrom = "/tabs/scan"; + this.router.navigate(['tabs/result']).then( () => { loading.dismiss(); } @@ -215,8 +189,7 @@ export class ScanPage { async presentLoading(msg: string): Promise { const loading = await this.loadingController.create({ - message: msg, - mode: "ios" + message: msg }); await loading.present(); return loading; @@ -232,41 +205,12 @@ export class ScanPage { async tapHaptic() { if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { - await Haptics.impact({ style: ImpactStyle.Medium }); + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) } } - - async showUpdateNotes() { - const alert = await this.alertController.create({ - header: this.translate.instant("UPDATE_NOTES"), - subHeader: this.env.appVersionNumber, - message: this.platform.is('ios')? this.translate.instant("UPDATE.UPDATE_NOTES_IOS") : this.translate.instant("UPDATE.UPDATE_NOTES_ANDROID"), - buttons: [this.translate.instant("OK")], - cssClass: ['left-align', 'alert-bg'] - }); - await alert.present(); - } - - async confirmExitApp(): Promise { - const alert = await this.alertController.create({ - header: this.translate.instant('EXIT_APP'), - message: this.translate.instant('MSG.EXIT_APP'), - cssClass: ['alert-bg'], - buttons: [ - { - text: this.translate.instant('YES'), - handler: () => { - navigator['app'].exitApp(); - } - }, - { - text: this.translate.instant('NO'), - role: 'cancel', - cssClass: 'btn-inverse' - } - ] - }); - await alert.present(); - } - } diff --git a/src/app/pages/setting-auto-brightness/setting-auto-brightness-routing.module.ts b/src/app/pages/setting-auto-brightness/setting-auto-brightness-routing.module.ts new file mode 100644 index 0000000..9d5065d --- /dev/null +++ b/src/app/pages/setting-auto-brightness/setting-auto-brightness-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingAutoBrightnessPage } from './setting-auto-brightness.page'; + +const routes: Routes = [ + { + path: '', + component: SettingAutoBrightnessPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingAutoBrightnessPageRoutingModule {} diff --git a/src/app/pages/setting-auto-brightness/setting-auto-brightness.module.ts b/src/app/pages/setting-auto-brightness/setting-auto-brightness.module.ts new file mode 100644 index 0000000..6c3374e --- /dev/null +++ b/src/app/pages/setting-auto-brightness/setting-auto-brightness.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingAutoBrightnessPageRoutingModule } from './setting-auto-brightness-routing.module'; + +import { SettingAutoBrightnessPage } from './setting-auto-brightness.page'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClient } from '@angular/common/http'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingAutoBrightnessPageRoutingModule + ], + declarations: [SettingAutoBrightnessPage] +}) +export class SettingAutoBrightnessPageModule {} diff --git a/src/app/pages/setting-auto-brightness/setting-auto-brightness.page.html b/src/app/pages/setting-auto-brightness/setting-auto-brightness.page.html new file mode 100644 index 0000000..f61e1b8 --- /dev/null +++ b/src/app/pages/setting-auto-brightness/setting-auto-brightness.page.html @@ -0,0 +1,37 @@ + + + + + + + {{ 'AUTO_MAX_BRIGHTNESS' | translate }} + + + + + + + +

+ +

+
+ +

+
+
+ + + +

+ + {{ (env.autoMaxBrightness == 'on'? 'TURNED_ON' : 'TURNED_OFF') | translate }} + +

+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/pages/setting-auto-brightness/setting-auto-brightness.page.scss b/src/app/pages/setting-auto-brightness/setting-auto-brightness.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/setting-auto-brightness/setting-auto-brightness.page.ts b/src/app/pages/setting-auto-brightness/setting-auto-brightness.page.ts new file mode 100644 index 0000000..1ffe1f4 --- /dev/null +++ b/src/app/pages/setting-auto-brightness/setting-auto-brightness.page.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Preferences } from '@capacitor/preferences'; +import { Toast } from '@capacitor/toast'; +import { EnvService } from 'src/app/services/env.service'; + +@Component({ + selector: 'app-setting-auto-brightness', + templateUrl: './setting-auto-brightness.page.html', + styleUrls: ['./setting-auto-brightness.page.scss'], +}) +export class SettingAutoBrightnessPage { + + constructor( + public env: EnvService, + ) { } + + async onAutoMaxBrightnessChange(ev: any) { + this.env.autoMaxBrightness = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_AUTO_MAX_BRIGHTNESS, value: this.env.autoMaxBrightness }); + await this.tapHaptic(); + } + + async tapHaptic() { + if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) + } + } +} diff --git a/src/app/pages/setting-auto-exit/setting-auto-exit-routing.module.ts b/src/app/pages/setting-auto-exit/setting-auto-exit-routing.module.ts new file mode 100644 index 0000000..d28c0b9 --- /dev/null +++ b/src/app/pages/setting-auto-exit/setting-auto-exit-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingAutoExitPage } from './setting-auto-exit.page'; + +const routes: Routes = [ + { + path: '', + component: SettingAutoExitPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingAutoExitPageRoutingModule {} diff --git a/src/app/pages/setting-auto-exit/setting-auto-exit.module.ts b/src/app/pages/setting-auto-exit/setting-auto-exit.module.ts new file mode 100644 index 0000000..32f5ac3 --- /dev/null +++ b/src/app/pages/setting-auto-exit/setting-auto-exit.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingAutoExitPageRoutingModule } from './setting-auto-exit-routing.module'; + +import { SettingAutoExitPage } from './setting-auto-exit.page'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClient } from '@angular/common/http'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingAutoExitPageRoutingModule + ], + declarations: [SettingAutoExitPage] +}) +export class SettingAutoExitPageModule {} diff --git a/src/app/pages/setting-auto-exit/setting-auto-exit.page.html b/src/app/pages/setting-auto-exit/setting-auto-exit.page.html new file mode 100644 index 0000000..b18c0e9 --- /dev/null +++ b/src/app/pages/setting-auto-exit/setting-auto-exit.page.html @@ -0,0 +1,75 @@ + + + + + + + {{ 'AUTO_KILL_BACKGROUND' | translate }} + + + + + + + +

+ +

+
+ +

+
+
+ + + + +

+ + {{ 'AT_LEAST_1_MINUTE_LATER' | translate }} + +

+
+ + +
+ + +

+ + {{ 'AT_LEAST_3_MINUTES_LATER' | translate }} + +

+
+ + +
+ + +

+ + {{ 'AT_LEAST_5_MINUTES_LATER' | translate }} + +

+
+ + +
+ + +

+ + {{ 'FOLLOW_SYSTEM_SETTINGS' | translate }} + +

+
+ + +
+
+ +
\ No newline at end of file diff --git a/src/app/pages/setting-auto-exit/setting-auto-exit.page.scss b/src/app/pages/setting-auto-exit/setting-auto-exit.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/setting-auto-exit/setting-auto-exit.page.ts b/src/app/pages/setting-auto-exit/setting-auto-exit.page.ts new file mode 100644 index 0000000..5b8bc2a --- /dev/null +++ b/src/app/pages/setting-auto-exit/setting-auto-exit.page.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; +import { Preferences } from '@capacitor/preferences'; +import { EnvService } from 'src/app/services/env.service'; + +@Component({ + selector: 'app-setting-auto-exit', + templateUrl: './setting-auto-exit.page.html', + styleUrls: ['./setting-auto-exit.page.scss'], +}) +export class SettingAutoExitPage{ + + constructor( + public env: EnvService, + ) { } + + async saveAutoExitAppMin() { + await Preferences.set({ key: this.env.KEY_AUTO_EXIT_MIN, value: JSON.stringify(this.env.autoExitAppMin) }); + } +} diff --git a/src/app/pages/setting-auto-open-url/setting-auto-open-url-routing.module.ts b/src/app/pages/setting-auto-open-url/setting-auto-open-url-routing.module.ts new file mode 100644 index 0000000..bfa4ff1 --- /dev/null +++ b/src/app/pages/setting-auto-open-url/setting-auto-open-url-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingAutoOpenUrlPage } from './setting-auto-open-url.page'; + +const routes: Routes = [ + { + path: '', + component: SettingAutoOpenUrlPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingAutoOpenUrlPageRoutingModule {} diff --git a/src/app/pages/setting-auto-open-url/setting-auto-open-url.module.ts b/src/app/pages/setting-auto-open-url/setting-auto-open-url.module.ts new file mode 100644 index 0000000..6b91d86 --- /dev/null +++ b/src/app/pages/setting-auto-open-url/setting-auto-open-url.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingAutoOpenUrlPageRoutingModule } from './setting-auto-open-url-routing.module'; + +import { SettingAutoOpenUrlPage } from './setting-auto-open-url.page'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { HttpLoaderFactory } from 'src/app/utils/helpers'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingAutoOpenUrlPageRoutingModule + ], + declarations: [SettingAutoOpenUrlPage] +}) +export class SettingAutoOpenUrlPageModule {} diff --git a/src/app/pages/setting-auto-open-url/setting-auto-open-url.page.html b/src/app/pages/setting-auto-open-url/setting-auto-open-url.page.html new file mode 100644 index 0000000..7db25a5 --- /dev/null +++ b/src/app/pages/setting-auto-open-url/setting-auto-open-url.page.html @@ -0,0 +1,37 @@ + + + + + + + {{ 'AUTO_OPEN_URL' | translate }} + + + + + + + +

+ +

+
+ +

+
+
+ + + +

+ + {{ (env.autoOpenUrl == 'on'? 'TURNED_ON' : 'TURNED_OFF') | translate }} + +

+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/pages/setting-auto-open-url/setting-auto-open-url.page.scss b/src/app/pages/setting-auto-open-url/setting-auto-open-url.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/setting-auto-open-url/setting-auto-open-url.page.ts b/src/app/pages/setting-auto-open-url/setting-auto-open-url.page.ts new file mode 100644 index 0000000..b3975dc --- /dev/null +++ b/src/app/pages/setting-auto-open-url/setting-auto-open-url.page.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Preferences } from '@capacitor/preferences'; +import { Toast } from '@capacitor/toast'; +import { EnvService } from 'src/app/services/env.service'; + +@Component({ + selector: 'app-setting-auto-open-url', + templateUrl: './setting-auto-open-url.page.html', + styleUrls: ['./setting-auto-open-url.page.scss'], +}) +export class SettingAutoOpenUrlPage { + + constructor( + public env: EnvService, + ) { } + + async onAutoOpenUrlChange(ev: any) { + this.env.autoOpenUrl = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_AUTO_OPEN_URL, value: this.env.autoOpenUrl }); + await this.tapHaptic(); + } + + async tapHaptic() { + if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) + } + } +} diff --git a/src/app/pages/setting-auto-qr/setting-auto-qr-routing.module.ts b/src/app/pages/setting-auto-qr/setting-auto-qr-routing.module.ts new file mode 100644 index 0000000..e38c7a4 --- /dev/null +++ b/src/app/pages/setting-auto-qr/setting-auto-qr-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingAutoQrPage } from './setting-auto-qr.page'; + +const routes: Routes = [ + { + path: '', + component: SettingAutoQrPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingAutoQrPageRoutingModule {} diff --git a/src/app/pages/setting-auto-qr/setting-auto-qr.module.ts b/src/app/pages/setting-auto-qr/setting-auto-qr.module.ts new file mode 100644 index 0000000..17834c0 --- /dev/null +++ b/src/app/pages/setting-auto-qr/setting-auto-qr.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingAutoQrPageRoutingModule } from './setting-auto-qr-routing.module'; + +import { SettingAutoQrPage } from './setting-auto-qr.page'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClient } from '@angular/common/http'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingAutoQrPageRoutingModule + ], + declarations: [SettingAutoQrPage] +}) +export class SettingAutoQrPageModule {} diff --git a/src/app/pages/setting-auto-qr/setting-auto-qr.page.html b/src/app/pages/setting-auto-qr/setting-auto-qr.page.html new file mode 100644 index 0000000..4ec840b --- /dev/null +++ b/src/app/pages/setting-auto-qr/setting-auto-qr.page.html @@ -0,0 +1,96 @@ + + + + + + + {{ 'AUTO_QR_CODE_POPUP' | translate }} + + + + + + + +

+ +

+
+ +

+
+
+ + + + + +

+ + {{ 'SCAN_BY_CAMERA' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'VIEW_LOG' | translate }} + +

+
+ + +
+ + + + +

+ + {{ 'VIEW_BOOKMARK' | translate }} + +

+
+ + +
+ + + + +

+ + {{ 'CREATE_QR_CODE' | translate }} + +

+
+ + +
+ + + + +

+ + {{ 'IMPORT_IMAGE' | translate }} + +

+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/pages/setting-auto-qr/setting-auto-qr.page.scss b/src/app/pages/setting-auto-qr/setting-auto-qr.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/setting-auto-qr/setting-auto-qr.page.ts b/src/app/pages/setting-auto-qr/setting-auto-qr.page.ts new file mode 100644 index 0000000..7303820 --- /dev/null +++ b/src/app/pages/setting-auto-qr/setting-auto-qr.page.ts @@ -0,0 +1,58 @@ +import { Component } from '@angular/core'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Preferences } from '@capacitor/preferences'; +import { Toast } from '@capacitor/toast'; +import { EnvService } from 'src/app/services/env.service'; + +@Component({ + selector: 'app-setting-auto-qr', + templateUrl: './setting-auto-qr.page.html', + styleUrls: ['./setting-auto-qr.page.scss'], +}) +export class SettingAutoQrPage { + + constructor( + public env: EnvService, + ) { } + + async onShowQrAfterCameraScanChange(ev: any) { + this.env.showQrAfterCameraScan = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_QR_AFTER_CAMERA_SCAN, value: this.env.showQrAfterCameraScan }); + await this.tapHaptic(); + } + + async onShowQrAfterImageScanChange(ev: any) { + this.env.showQrAfterImageScan = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_QR_AFTER_IMAGE_SCAN, value: this.env.showQrAfterImageScan }); + await this.tapHaptic(); + } + + async onShowQrAfterCreateChange(ev: any) { + this.env.showQrAfterCreate = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_QR_AFTER_CREATE, value: this.env.showQrAfterCreate }); + await this.tapHaptic(); + } + + async onShowQrAfterLogViewChange(ev: any) { + this.env.showQrAfterLogView = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_QR_AFTER_LOG_VIEW, value: this.env.showQrAfterLogView }); + await this.tapHaptic(); + } + + async onShowQrAfterBookmarkViewChange(ev: any) { + this.env.showQrAfterBookmarkView = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_QR_AFTER_BOOKMARK_VIEW, value: this.env.showQrAfterBookmarkView }); + await this.tapHaptic(); + } + + async tapHaptic() { + if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) + } + } +} diff --git a/src/app/pages/setting-color/setting-color.page.html b/src/app/pages/setting-color/setting-color.page.html index 7322e89..d523f7e 100644 --- a/src/app/pages/setting-color/setting-color.page.html +++ b/src/app/pages/setting-color/setting-color.page.html @@ -1,7 +1,7 @@ - - + + - + {{ 'COLOR_THEME' | translate }} @@ -11,54 +11,54 @@ - + -

+

{{ 'SYSTEM_DEFAULT' | translate }}

-
- + -

+

{{ 'LIGHT' | translate }}

-
- + -

+

{{ 'DARK' | translate }}

-
- + -

+

{{ 'BLACK' | translate }}

- diff --git a/src/app/pages/setting-color/setting-color.page.ts b/src/app/pages/setting-color/setting-color.page.ts index a31cf72..277109b 100644 --- a/src/app/pages/setting-color/setting-color.page.ts +++ b/src/app/pages/setting-color/setting-color.page.ts @@ -1,5 +1,6 @@ import { OverlayContainer } from '@angular/cdk/overlay'; import { Component } from '@angular/core'; +import { Preferences } from '@capacitor/preferences'; import { TranslateService } from '@ngx-translate/core'; import { EnvService } from 'src/app/services/env.service'; @@ -18,7 +19,7 @@ export class SettingColorPage { async saveColorTheme() { await this.env.toggleColorTheme(); - await this.env.storageSet("color", this.env.selectedColorTheme); + await Preferences.set({ key: this.env.KEY_COLOR, value: this.env.selectedColorTheme }); } } diff --git a/src/app/pages/setting-debug/setting-debug.page.html b/src/app/pages/setting-debug/setting-debug.page.html index cb4f6dc..32de1b9 100644 --- a/src/app/pages/setting-debug/setting-debug.page.html +++ b/src/app/pages/setting-debug/setting-debug.page.html @@ -1,7 +1,7 @@ - - + + - + {{ 'DEBUG_MODE' | translate }} @@ -10,8 +10,8 @@ - - + +

@@ -19,11 +19,11 @@

-
- +

@@ -31,7 +31,7 @@

-
diff --git a/src/app/pages/setting-debug/setting-debug.page.ts b/src/app/pages/setting-debug/setting-debug.page.ts index d7cac7e..96e8118 100644 --- a/src/app/pages/setting-debug/setting-debug.page.ts +++ b/src/app/pages/setting-debug/setting-debug.page.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { Preferences } from '@capacitor/preferences'; import { TranslateService } from '@ngx-translate/core'; import { EnvService } from 'src/app/services/env.service'; @@ -15,7 +16,7 @@ export class SettingDebugPage { ) { } async saveDebugMode() { - await this.env.storageSet("debug-mode-on", this.env.debugModeOn); + await Preferences.set({ key: this.env.KEY_DEBUG_MODE, value: this.env.debugMode }); } } diff --git a/src/app/pages/setting-language/setting-language.page.html b/src/app/pages/setting-language/setting-language.page.html index 6dc77d0..5fc944e 100644 --- a/src/app/pages/setting-language/setting-language.page.html +++ b/src/app/pages/setting-language/setting-language.page.html @@ -1,7 +1,7 @@ - - + + - + {{ 'LANGUAGE' | translate }} @@ -11,57 +11,104 @@ - + -

+

{{ 'SYSTEM_DEFAULT' | translate }}

- + -
- + -

+

- English + Deutsch (de)

- + -
- + -

+

- 正體中文 + English (en)

- + -
- + -

+

- 简体中文 + Français (fr)

- + + +
+ + +

+ + Italiano (it) + +

+
+ + +
+ + +

+ + Português do Brasil (pt-BR) + +

+
+ + +
+ + +

+ + Русский (ru) + +

+
+ + +
+ + +

+ + 中文 (香港) (zh-HK) + +

+
+ + +
+ + +

+ + 简体中文 (zh-CN) + +

+
+ -
diff --git a/src/app/pages/setting-language/setting-language.page.ts b/src/app/pages/setting-language/setting-language.page.ts index f82e486..b6b0507 100644 --- a/src/app/pages/setting-language/setting-language.page.ts +++ b/src/app/pages/setting-language/setting-language.page.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { Preferences } from '@capacitor/preferences'; import { TranslateService } from '@ngx-translate/core'; import { EnvService } from 'src/app/services/env.service'; @@ -16,6 +17,6 @@ export class SettingLanguagePage { async saveLanguage() { this.env.toggleLanguageChange(); - await this.env.storageSet("language", this.env.selectedLanguage); + await Preferences.set({ key: this.env.KEY_LANGUAGE, value: this.env.selectedLanguage }); } } diff --git a/src/app/pages/setting-orientation/setting-orientation-routing.module.ts b/src/app/pages/setting-orientation/setting-orientation-routing.module.ts new file mode 100644 index 0000000..9e14f0e --- /dev/null +++ b/src/app/pages/setting-orientation/setting-orientation-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingOrientationPage } from './setting-orientation.page'; + +const routes: Routes = [ + { + path: '', + component: SettingOrientationPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingOrientationPageRoutingModule {} diff --git a/src/app/pages/setting-orientation/setting-orientation.module.ts b/src/app/pages/setting-orientation/setting-orientation.module.ts new file mode 100644 index 0000000..50d69e0 --- /dev/null +++ b/src/app/pages/setting-orientation/setting-orientation.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingOrientationPageRoutingModule } from './setting-orientation-routing.module'; + +import { SettingOrientationPage } from './setting-orientation.page'; +import { HttpClient } from '@angular/common/http'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingOrientationPageRoutingModule + ], + declarations: [SettingOrientationPage] +}) +export class SettingOrientationPageModule {} diff --git a/src/app/pages/setting-orientation/setting-orientation.page.html b/src/app/pages/setting-orientation/setting-orientation.page.html new file mode 100644 index 0000000..fccbae1 --- /dev/null +++ b/src/app/pages/setting-orientation/setting-orientation.page.html @@ -0,0 +1,49 @@ + + + + + + + {{ 'SCREEN_ORIENTATION' | translate }} + + + + + + + + +

+ + {{ 'SYSTEM_DEFAULT' | translate }} + +

+
+ + +
+ + +

+ + {{ 'LOCK_PORTRAIT' | translate }} + +

+
+ + +
+ + +

+ + {{ 'LOCK_LANDSCAPE' | translate }} + +

+
+ + +
+
+ +
\ No newline at end of file diff --git a/src/app/pages/setting-orientation/setting-orientation.page.scss b/src/app/pages/setting-orientation/setting-orientation.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/setting-orientation/setting-orientation.page.ts b/src/app/pages/setting-orientation/setting-orientation.page.ts new file mode 100644 index 0000000..4d357d5 --- /dev/null +++ b/src/app/pages/setting-orientation/setting-orientation.page.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; +import { Preferences } from '@capacitor/preferences'; +import { EnvService } from 'src/app/services/env.service'; + +@Component({ + selector: 'app-setting-orientation', + templateUrl: './setting-orientation.page.html', + styleUrls: ['./setting-orientation.page.scss'], +}) +export class SettingOrientationPage { + + constructor( + public env: EnvService, + ) { } + + async saveOrientation() { + await this.env.toggleOrientationChange(); + await Preferences.set({ key: this.env.KEY_ORIENTATION, value: this.env.orientation }); + } +} diff --git a/src/app/pages/setting-qr/setting-qr-routing.module.ts b/src/app/pages/setting-qr/setting-qr-routing.module.ts new file mode 100644 index 0000000..54cc0d3 --- /dev/null +++ b/src/app/pages/setting-qr/setting-qr-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingQrPage } from './setting-qr.page'; + +const routes: Routes = [ + { + path: '', + component: SettingQrPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingQrPageRoutingModule {} diff --git a/src/app/pages/setting-qr/setting-qr.module.ts b/src/app/pages/setting-qr/setting-qr.module.ts new file mode 100644 index 0000000..f6c53ca --- /dev/null +++ b/src/app/pages/setting-qr/setting-qr.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingQrPageRoutingModule } from './setting-qr-routing.module'; + +import { SettingQrPage } from './setting-qr.page'; +import { HttpClient } from '@angular/common/http'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { QRCodeModule } from 'angularx-qrcode'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + QRCodeModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingQrPageRoutingModule + ], + declarations: [SettingQrPage] +}) +export class SettingQrPageModule {} diff --git a/src/app/pages/setting-qr/setting-qr.page.html b/src/app/pages/setting-qr/setting-qr.page.html new file mode 100644 index 0000000..f7899aa --- /dev/null +++ b/src/app/pages/setting-qr/setting-qr.page.html @@ -0,0 +1,220 @@ + + + + + + + {{ 'QR_CODE_STYLE' | translate }} + + + +
+ + + + + {{ 'PREVIEW' | translate }} + + +
+ + + + + + + + + + {{ qrCodeContent }} + + + +
+
+
+
+ + + +
+ + + + + {{ 'ERROR_CORRECTION_LEVEL' | translate }} + + + + +

+ +

+
+ +

+
+
+
+
+ + + +

+ + {{ 'LEVEL_L' | translate }} + +

+
+ + +
+ + +

+ + {{ 'LEVEL_M' | translate }} + +

+
+ + +
+ + +

+ + {{ 'LEVEL_Q' | translate }} + +

+
+ + +
+ + +

+ + {{ 'LEVEL_H' | translate }} + +

+
+ + +
+
+
+ +
+ +
+ + {{ 'COLOR' | translate }} + + + + + +
+
+ + + R + {{ env.qrCodeDarkR }} + + + + + G + {{ env.qrCodeDarkG }} + + + + + B + {{ env.qrCodeDarkB }} + + +
+ +
+ +
+ + {{ 'BACKGROUND_COLOR' | translate }} + + + + + +
+
+ + + R + {{ env.qrCodeLightR }} + + + + + G + {{ env.qrCodeLightG }} + + + + + B + {{ env.qrCodeLightB }} + + +
+ +
+ +
+ + {{ 'MARGIN' | translate }} + + + + + +
+
+ + + {{ env.qrCodeMargin }} + + +
+ + +
+ + + + + {{ 'RESET_DEFAULT' | translate }} + + + \ No newline at end of file diff --git a/src/app/pages/setting-qr/setting-qr.page.scss b/src/app/pages/setting-qr/setting-qr.page.scss new file mode 100644 index 0000000..9f38ea7 --- /dev/null +++ b/src/app/pages/setting-qr/setting-qr.page.scss @@ -0,0 +1,4 @@ +.top-qr-preview { + background: var(--ion-background-color, #fff); + color: var(--ion-text-color, #000); +} diff --git a/src/app/pages/setting-qr/setting-qr.page.ts b/src/app/pages/setting-qr/setting-qr.page.ts new file mode 100644 index 0000000..257b5b2 --- /dev/null +++ b/src/app/pages/setting-qr/setting-qr.page.ts @@ -0,0 +1,146 @@ +import { Component } from '@angular/core'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Preferences } from '@capacitor/preferences'; +import { Toast } from '@capacitor/toast'; +import { AlertController, Platform } from '@ionic/angular'; +import { TranslateService } from '@ngx-translate/core'; +import { QRCodeElementType } from 'angularx-qrcode'; +import { EnvService } from 'src/app/services/env.service'; +import { rgbToHex } from 'src/app/utils/helpers'; + +@Component({ + selector: 'app-setting-qr', + templateUrl: './setting-qr.page.html', + styleUrls: ['./setting-qr.page.scss'], +}) +export class SettingQrPage { + + qrCodeContent: string = 'https://github.com/tomfong/simple-qr'; + qrElementType: QRCodeElementType = "canvas"; + errorCorrectionLevel: 'low' | 'medium' | 'quartile' | 'high' | 'L' | 'M' | 'Q' | 'H'; + readonly MAX_WIDTH = 300; + defaultWidth: number = window.innerWidth * 0.4 > this.MAX_WIDTH ? this.MAX_WIDTH : window.innerWidth * 0.4; + + colorLocked: boolean = true; + backgroundColorLocked: boolean = true; + marginLocked: boolean = true; + + constructor( + public env: EnvService, + private translate: TranslateService, + private alertController: AlertController, + private platform: Platform, + ) { + this.setErrorCorrectionLevel(); + } + + ionViewWillLeave() { + this.colorLocked = true; + this.backgroundColorLocked = true; + this.marginLocked = true; + } + + get qrColorDark(): string { + return rgbToHex(this.env.qrCodeDarkR, this.env.qrCodeDarkG, this.env.qrCodeDarkB); + } + + get qrColorLight(): string { + return rgbToHex(this.env.qrCodeLightR, this.env.qrCodeLightG, this.env.qrCodeLightB); + } + + setErrorCorrectionLevel() { + switch (this.env.errorCorrectionLevel) { + case 'L': + this.errorCorrectionLevel = 'low'; + break; + case 'M': + this.errorCorrectionLevel = 'medium'; + break; + case 'Q': + this.errorCorrectionLevel = 'quartile'; + break; + case 'H': + this.errorCorrectionLevel = 'high'; + break; + default: + this.errorCorrectionLevel = 'medium'; + } + } + + async saveErrorCorrectionLevel() { + this.setErrorCorrectionLevel(); + await Preferences.set({ key: this.env.KEY_ERROR_CORRECTION_LEVEL, value: this.env.errorCorrectionLevel }); + } + + async saveQrCodeDarkR() { + await Preferences.set({ key: this.env.KEY_QR_CODE_DARK_R, value: JSON.stringify(this.env.qrCodeDarkR) }); + } + + async saveQrCodeDarkG() { + await Preferences.set({ key: this.env.KEY_QR_CODE_DARK_G, value: JSON.stringify(this.env.qrCodeDarkG) }); + } + + async saveQrCodeDarkB() { + await Preferences.set({ key: this.env.KEY_QR_CODE_DARK_B, value: JSON.stringify(this.env.qrCodeDarkB) }); + } + + async saveQrCodeLightR() { + await Preferences.set({ key: this.env.KEY_QR_CODE_LIGHT_R, value: JSON.stringify(this.env.qrCodeLightR) }); + } + + async saveQrCodeLightG() { + await Preferences.set({ key: this.env.KEY_QR_CODE_LIGHT_G, value: JSON.stringify(this.env.qrCodeLightG) }); + } + + async saveQrCodeLightB() { + await Preferences.set({ key: this.env.KEY_QR_CODE_LIGHT_B, value: JSON.stringify(this.env.qrCodeLightB) }); + } + + async saveQrCodeMargin() { + await Preferences.set({ key: this.env.KEY_QR_CODE_MARGIN, value: JSON.stringify(this.env.qrCodeMargin) }); + } + + async resetDefault() { + const alert = await this.alertController.create({ + header: this.translate.instant('RESET_DEFAULT'), + message: this.translate.instant('MSG.RESET_DEFAULT'), + cssClass: ['alert-bg'], + buttons: [ + { + text: this.translate.instant('YES'), + handler: async () => { + this.colorLocked = true; + this.backgroundColorLocked = true; + this.marginLocked = true; + await this.env.resetQrCodeSettings(); + } + }, + { + text: this.translate.instant('NO'), + handler: () => true + } + ] + }); + await alert.present(); + } + + async tapHaptic() { + if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) + } + } + + get isAndroid(): boolean { + return this.platform.is('android'); + } + + get isIos(): boolean { + return this.platform.is('ios'); + } + +} diff --git a/src/app/pages/setting-record/setting-record.page.html b/src/app/pages/setting-record/setting-record.page.html index 6e9ee0c..b98f642 100644 --- a/src/app/pages/setting-record/setting-record.page.html +++ b/src/app/pages/setting-record/setting-record.page.html @@ -1,88 +1,255 @@ - - + + - + - {{ 'LOGGING_BACKUP_RESTORE' | translate }} + {{ 'LOG_BACKUP_AND_RESTORE' | translate }} -
- {{ 'AUTO_LOGGING' | translate }} - - -

- - {{ 'AUTO_LOGGING_EXPLAIN' | translate }} - -

-
-
- - - -

+

+ {{ 'INITIAL_SEGMENT' | translate }} + + + + + + +

- {{ 'TURN_ON' | translate }} + {{ 'LOG' | translate }}

- + -
- - -

+ + + + +

- {{ 'TURN_OFF' | translate }} + {{ 'BOOKMARK' | translate }}

- + -
-
- {{ 'BACKUP' | translate }} +
+ {{ 'MANAGE_RECORDS' | translate }} + +
+ + {{ 'VIEW_INSTRUCTIONS' | translate }} + +
+
+ + + + + {{ 'TUTORIAL' | translate }} + + + +

+ + {{ 'MSG.TUTORIAL_SWIPE_LEFT' | translate }} + +

+
+
+ + + +

+ + {{ 'MSG.TUTORIAL_SWIPE_RIGHT' | translate }} + +

+
+
+
+
+
+ +
+ {{ 'AUTO_LOGGING' | translate }} +

- {{ 'BACKUP_EXPLAIN' | translate }} + {{ 'MSG.AUTO_LOGGING_EXPLAIN' | translate }}

-
+ + +

+ + {{ (env.scanRecordLogging == 'on'? 'TURNED_ON' : 'TURNED_OFF') | translate }} + +

+
+ + +
+
+ +
+ {{ 'RECORDS_LIMIT' | translate }} + + + +

+ + {{ 'MSG.RECORDS_LIMIT_EXPLAIN' | translate }} + +

+
+
+ + + +

+ + {{ '30_RECORDS' | translate }} + +

+
+ + +
+ + +

+ + {{ '50_RECORDS' | translate }} + +

+
+ + +
+ + +

+ + {{ '100_RECORDS' | translate }} + +

+
+ + +
+ + +

+ + {{ 'NO_LIMIT' | translate }} + +

+
+ + +
+
+ + +

+ + {{ 'SHOW_NUMBER_OR_RECORDS' | translate }} + +

+
+ + +
+
+ +
+ {{ 'BACKUP' | translate }} + + +

+ + {{ 'MSG.BACKUP_EXPLAIN' | translate }} + +

+
+
+
{{ 'BACKUP' | translate }}
-
- {{ 'RESTORE' | translate }} +
+ {{ 'RESTORE' | translate }}

- {{ (isIOS? 'RESTORE_EXPLAIN_IOS' : 'RESTORE_EXPLAIN') | translate }} + {{ (isIOS? 'MSG.RESTORE_EXPLAIN_IOS' : 'MSG.RESTORE_EXPLAIN') | translate }}

-
+
{{ 'RESTORE' | translate }}
+
+ {{ 'EXPORT' | translate }} + + +

+ + {{ 'MSG.EXPORT_TO_CSV_EXPLAIN' | translate }} + +

+
+
+
+ + {{ 'EXPORT_TO_CSV' | translate }} + +
+
+ + + + \ No newline at end of file diff --git a/src/app/pages/setting-record/setting-record.page.scss b/src/app/pages/setting-record/setting-record.page.scss index e69de29..2c16e8d 100644 --- a/src/app/pages/setting-record/setting-record.page.scss +++ b/src/app/pages/setting-record/setting-record.page.scss @@ -0,0 +1,8 @@ +mat-form-field { + width: 100%; +} + +.content-item { + padding-left: 16px; + padding-right: 16px; +} diff --git a/src/app/pages/setting-record/setting-record.page.ts b/src/app/pages/setting-record/setting-record.page.ts index 0870efa..04f57b6 100644 --- a/src/app/pages/setting-record/setting-record.page.ts +++ b/src/app/pages/setting-record/setting-record.page.ts @@ -1,16 +1,19 @@ import { Component } from '@angular/core'; -import { AlertController, LoadingController, Platform } from '@ionic/angular'; +import { AlertController, LoadingController, ModalController, Platform } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; import { EnvService } from 'src/app/services/env.service'; import { Clipboard } from '@capacitor/clipboard'; import { Toast } from '@capacitor/toast'; import { EncryptService } from 'src/app/services/encrypt.service'; import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'; -import * as moment from 'moment'; +import { format } from 'date-fns'; import { Chooser, ChooserResult } from '@awesome-cordova-plugins/chooser/ngx'; import { ScanRecord } from 'src/app/models/scan-record'; import { Bookmark } from 'src/app/models/bookmark'; import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Preferences } from '@capacitor/preferences'; +import { de, enUS, fr, it, ptBR, ru, zhCN, zhHK } from 'date-fns/locale'; @Component({ selector: 'app-setting-record', @@ -19,6 +22,8 @@ import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; }) export class SettingRecordPage { + preventRecordsLimitToast: boolean = true; + constructor( public translate: TranslateService, public env: EnvService, @@ -27,11 +32,39 @@ export class SettingRecordPage { private loadingController: LoadingController, private chooser: Chooser, private socialSharing: SocialSharing, - private platform: Platform + private platform: Platform, + private modalController: ModalController, ) { } - async saveScanRecord() { - await this.env.storageSet("scan-record-logging", this.env.scanRecordLogging); + ionViewDidEnter() { + setTimeout(() => this.preventRecordsLimitToast = false, 100); + } + + ionViewWillLeave() { + this.preventRecordsLimitToast = true; + } + + async saveHistoryPageStartSegment() { + await Preferences.set({ key: this.env.KEY_HISTORY_PAGE_START_SEGMENT, value: this.env.historyPageStartSegment }); + } + + async onScanRecordLoggingChange(ev: any) { + this.env.scanRecordLogging = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SCAN_RECORD_LOGGING, value: this.env.scanRecordLogging }); + await this.tapHaptic(); + } + + async saveRecordsLimit() { + await Preferences.set({ key: this.env.KEY_RECORDS_LIMIT, value: JSON.stringify(this.env.recordsLimit) }); + if (this.env.recordsLimit != -1 && !this.preventRecordsLimitToast) { + this.presentToast(this.translate.instant("MSG.DELETE_OVERFLOWED_RECORDS"), "short", "bottom"); + } + } + + async onShowNumberOfRecordsChange(ev: any) { + this.env.showNumberOfRecords = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_NUMBER_OF_RECORDS, value: this.env.showNumberOfRecords }); + await this.tapHaptic(); } async onBackup() { @@ -45,7 +78,7 @@ export class SettingRecordPage { async (value) => { loading1.dismiss(); const loading2 = await this.presentLoading(this.translate.instant("BACKING_UP")); - const now = moment().format("yyyyMMDDHHmmss"); + const now = format(new Date(), "yyyyMMddHHmmss"); const filename = this.platform.is('ios') ? `i-simpleqr-backup-${now}.isqbk` : `simpleqr-backup-${now}.tfsqbk`; await Filesystem.writeFile({ path: `${filename}`, @@ -65,14 +98,26 @@ export class SettingRecordPage { cssClass: ['alert-bg', 'alert-can-copy'], buttons: [ { - text: this.translate.instant('COPY_SECRET_SHARE'), + text: this.translate.instant('COPY_SECRET_AND_SAVE_BACKUP'), handler: async () => { await Clipboard.write({ string: secret }).then( async () => { await this.presentToast(this.translate.instant('MSG.COPIED_SECRET'), "short", "bottom"); } ); - await this.socialSharing.share(null, filename, result.uri, null); + const loading3 = await this.presentLoading(this.translate.instant("PLEASE_WAIT")); + await this.socialSharing.share(null, filename, result.uri, null).then( + _ => { + loading3.dismiss(); + } + ).catch( + err => { + loading3.dismiss(); + if (this.env.isDebugging) { + this.presentToast("Error when SocialSharing.share: " + JSON.stringify(err), "long", "top"); + } + } + ) } } ] @@ -82,23 +127,32 @@ export class SettingRecordPage { } ).catch( err => { - console.error("err in write file", err) loading2.dismiss(); - this.presentToast(this.env.debugModeOn === 'on' ? this.translate.instant("MSG.BACKUP_FAILED_2") + ' (write file failed)' : this.translate.instant("MSG.BACKUP_FAILED_2"), "short", "bottom"); + if (this.env.isDebugging) { + this.presentToast("Error when call Filesystem.writeFile: " + JSON.stringify(err), "long", "top"); + } else { + this.presentToast(this.translate.instant("MSG.BACKUP_FAILED_2"), "short", "bottom"); + } } ) } ).catch( err => { loading1.dismiss(); - this.presentToast(this.env.debugModeOn === 'on' ? this.translate.instant("MSG.BACKUP_FAILED") + ' (encrypt failed)' : this.translate.instant("MSG.BACKUP_FAILED"), "short", "bottom"); + if (this.env.isDebugging) { + this.presentToast("Error when encrypt: " + JSON.stringify(err), "long", "top"); + } else { + this.presentToast(this.translate.instant("MSG.BACKUP_FAILED"), "short", "bottom"); + } } ) } async onRestore() { + const loading1 = await this.presentLoading(this.translate.instant("PLEASE_WAIT")); await this.chooser.getFile().then( async (value: ChooserResult) => { + loading1.dismiss(); if (value == null) { return; } @@ -113,11 +167,13 @@ export class SettingRecordPage { return; } } + const loading2 = await this.presentLoading(this.translate.instant("PLEASE_WAIT")); await Filesystem.readFile({ - path: value.uri, + path: value['uri'], encoding: Encoding.UTF8 }).then( async value => { + loading2.dismiss(); const alert = await this.alertController.create( { header: this.translate.instant('RESTORE'), @@ -128,8 +184,8 @@ export class SettingRecordPage { name: 'secret', id: 'secret', type: 'text', - label: `${this.translate.instant("SECRET")} (${this.translate.instant("LENGTH_49")})`, - placeholder: `${this.translate.instant("SECRET")} (${this.translate.instant("LENGTH_49")})`, + label: `${this.translate.instant("SECRET")} (${this.translate.instant("49_DIGIT")})`, + placeholder: `${this.translate.instant("SECRET")} (${this.translate.instant("49_DIGIT")})`, max: 49, min: 49, } @@ -140,7 +196,11 @@ export class SettingRecordPage { handler: async data => { alert.dismiss(); if (data.secret != null && data.secret.trim().length == 49) { - await this.restore(value.data, data.secret.trim()); + if ((typeof value.data) == 'string') { + await this.restore(value.data as string, data.secret.trim()); + } else { + this.presentToast(this.translate.instant("MSG.RESTORE_FAILED"), "short", "bottom"); + } } else { this.presentToast(this.translate.instant("MSG.PLEASE_INPUT_VALID_SECRET"), "short", "bottom"); } @@ -153,7 +213,8 @@ export class SettingRecordPage { } ).catch( err => { - if (this.env.debugModeOn === 'on') { + loading2.dismiss(); + if (this.env.debugMode === 'on') { this.presentToast('Failed to read file', "long", "bottom"); } else { this.presentToast(this.translate.instant("MSG.RESTORE_FAILED"), "short", "bottom"); @@ -163,7 +224,12 @@ export class SettingRecordPage { } ).catch( err => { - this.presentToast(this.translate.instant("MSG.RESTORE_FAILED"), "short", "bottom"); + loading1.dismiss(); + if (this.env.isDebugging) { + this.presentToast("Error when call Chooser.getFile: " + JSON.stringify(err), "long", "top"); + } else { + this.presentToast(this.translate.instant("MSG.RESTORE_FAILED"), "short", "bottom"); + } } ) } @@ -174,9 +240,11 @@ export class SettingRecordPage { this.presentToast(this.translate.instant("MSG.PLEASE_INPUT_VALID_SECRET"), "short", "bottom"); return; } + const loading = await this.presentLoading(this.translate.instant("DECRYPTING")); await this.encryptService.decrypt(value, secrets[0], secrets[1]) .then( async value => { + loading.dismiss(); try { const restore = JSON.parse(value); if (restore.application != "Simple QR") { @@ -216,17 +284,264 @@ export class SettingRecordPage { ) await alert.present(); } catch (err) { - console.error("error...", err) - this.presentToast(this.translate.instant("MSG.RESTORE_FAILED"), "short", "bottom"); + if (this.env.isDebugging) { + this.presentToast("Error when encrypt: " + JSON.stringify(err), "long", "top"); + } else { + this.presentToast(this.translate.instant("MSG.RESTORE_FAILED"), "short", "bottom"); + } } } ) .catch(err => { - console.error("error when decrypt", err) - this.presentToast(this.translate.instant("MSG.RESTORE_WRONG_SECRET"), "short", "bottom"); + loading.dismiss(); + if (this.env.isDebugging) { + this.presentToast("Error when parsing: " + JSON.stringify(err), "long", "top"); + } else { + this.presentToast(this.translate.instant("MSG.RESTORE_WRONG_SECRET"), "short", "bottom"); + } }); } + async onExportToCsv() { + const loading = await this.presentLoading(this.translate.instant("EXPORTING")); + const now = format(new Date(), "yyyyMMddHHmmss"); + const filename = `simpleqr-${now}.csv`; + let rawCsvData: string; + switch (this.env.language) { + case "de": + rawCsvData = "ID,Inhalt,Erstellt um,Quelle,Barcode-Typ,Lesezeichen gesetzt?,Etikett\r\n"; + break; + case "en": + rawCsvData = "ID,Content,Created at,Source,Barcode Type,Bookmarked?,Tag\r\n"; + break; + case "fr": + rawCsvData = "ID,Le contenu,Créé à,La source,Type de code-barres,En signet?,Étiquette\r\n"; + break; + case "it": + rawCsvData = "ID,Contenuto,Creato a,Fonte,Tipo di codice a barre,Aggiunto ai preferiti?,Etichetta\r\n"; + break; + case "pt-BR": + rawCsvData = "ID,Conteúdo,Criado em,Origem,Tipo de código de barras,Marcado como favorito?,Tag\r\n"; + break; + case "ru": + rawCsvData = "ID,Содержание,Создано в,Источник,Тип штрих-кода,В закладках?,Ярлык\r\n"; + break; + case "zh-CN": + rawCsvData = "ID,内容,建立于,来源,条码类型,已书签?,标签\r\n"; + break; + case "zh-HK": + rawCsvData = "ID,内容,建立於,來源,條碼類型,已書籤?,標籤\r\n"; + break; + default: + rawCsvData = "ID,Content,Created at,Source,Barcode Type,Bookmarked?,Tag\r\n"; + } + this.env.scanRecords.forEach(r => { + rawCsvData += `"${r.id}","${r.text?.split('"').join('') ?? ""}","${this.maskDatetime(r.createdAt)}","${this.maskSource(r.source)}","${r.barcodeType ?? ''}",` + const bookmark = this.env.bookmarks.find(b => b.text == r.text); + if (bookmark != null) { + rawCsvData += `"TRUE","${bookmark.tag?.split('"').join('') ?? ""}"\r\n`; + } else { + rawCsvData += `"FALSE",""\r\n`; + } + }); + this.env.bookmarks.forEach(b => { + if (this.env.scanRecords.findIndex(r => r.text == b.text) == -1) { + rawCsvData += `"-","${b.text?.split('"').join('') ?? ""}","${this.maskDatetime(b.createdAt)}","-","-","TRUE","${b.tag?.split('"').join('') ?? ""}"\r\n` + } + }); + await Filesystem.writeFile({ + path: `${filename}`, + data: rawCsvData, + directory: Directory.External, + encoding: Encoding.UTF8, + recursive: true + }).then( + async result => { + loading.dismiss(); + const loading2 = await this.presentLoading(this.translate.instant("PLEASE_WAIT")); + await this.socialSharing.share(null, filename, result.uri, null).then(() => { + loading2.dismiss(); + }).catch( + err => { + loading2.dismiss(); + if (this.env.isDebugging) { + this.presentToast("Error when SocialSharing.share: " + JSON.stringify(err), "long", "top"); + } + } + ); + } + ).catch( + err => { + loading.dismiss(); + if (this.env.isDebugging) { + this.presentToast("Error when call Filesystem.writeFile: " + JSON.stringify(err), "long", "top"); + } else { + this.presentToast("Error!", "short", "bottom"); + } + } + ); + } + + // async onImportFromCsv() { + // // TODO: Import from CSV + // const loading1 = await this.presentLoading(this.translate.instant("PLEASE_WAIT")); + // await this.chooser.getFile().then( + // async (value: ChooserResult) => { + // if (value == null) { + // loading1.dismiss(); + // return; + // } + // if (!value.name.toLowerCase().endsWith(".csv")) { + // loading1.dismiss(); + // this.presentToast(`${this.translate.instant("MSG.INVALID_CSV_FILE")} (1)`, "short", "bottom"); + // return; + // } + // await Filesystem.readFile({ + // path: value.uri, + // encoding: Encoding.UTF8 + // }).then( + // async value => { + // loading1.dismiss(); + // const loading2 = await this.presentLoading(this.translate.instant("DECODING")); + // const data = value.data; + // if (data.length == 0) { + // loading2.dismiss(); + // this.presentToast(`${this.translate.instant("MSG.INVALID_CSV_FILE")} (2)`, "short", "bottom"); + // return; + // } + // const lines = data.split("\r\n"); + // if (lines.length <= 1) { + // loading2.dismiss(); + // this.presentToast(`${this.translate.instant("MSG.INVALID_CSV_FILE")} (3)`, "short", "bottom"); + // return; + // } + // const scanRecords = []; + // for (var i = 1; i < lines.length; i++) { + // const line = lines[i]; + // if (line.length == 0) { + // loading2.dismiss(); + // this.presentToast(`${this.translate.instant("MSG.INVALID_CSV_FILE")} (4)`, "short", "bottom"); + // return; + // } + // const items = line.split(`","`); + // if (items.length != 7) { + // loading2.dismiss(); + // this.presentToast(`${this.translate.instant("MSG.INVALID_CSV_FILE")} (5)`, "short", "bottom"); + // return; + // } + // const id = items[0].replace(`"`, ""); + // if (isNaN(parseInt(id))) { + // loading2.dismiss(); + // this.presentToast(`${this.translate.instant("MSG.INVALID_CSV_FILE")} (6)`, "short", "bottom"); + // return; + // } + // } + // if (scanRecords.length > 0) { + // await this.env.saveRestoredScanRecords(scanRecords); + // } + // } + // ).catch( + // err => { + // loading1.dismiss(); + // if (this.env.debugMode === 'on') { + // this.presentToast('Failed to read file', "long", "bottom"); + // } else { + // this.presentToast(this.translate.instant("MSG.IMPORT_FAILED"), "short", "bottom"); + // } + // } + // ) + // } + // ).catch( + // err => { + // loading1.dismiss(); + // if (this.env.isDebugging) { + // this.presentToast("Error when call Chooser.getFile: " + JSON.stringify(err), "long", "top"); + // } else { + // this.presentToast(this.translate.instant("MSG.IMPORT_FAILED"), "short", "bottom"); + // } + // } + // ) + // } + + maskDatetime(date: Date): string { + if (!date) { + return "-"; + } + let locale: Locale; + switch (this.env.language) { + case "de": + locale = de; + break; + case "en": + locale = enUS; + break; + case "fr": + locale = fr; + break; + case "it": + locale = it; + break; + case "pt-BR": + locale = ptBR; + break; + case "ru": + locale = ru; + break; + case "zh-CN": + locale = zhCN; + break; + case "zh-HK": + locale = zhHK; + break; + default: + locale = enUS; + } + return format(date, "PP pp", { locale: locale }); + } + + maskSource(source: 'create' | 'view' | 'scan' | undefined): string { + if (source == null) { + return "-"; + } + let locale: Locale; + switch (this.env.language) { + case "de": + locale = de; + break; + case "en": + locale = enUS; + break; + case "fr": + locale = fr; + break; + case "it": + locale = it; + break; + case "pt-BR": + locale = ptBR; + break; + case "ru": + locale = ru; + break; + case "zh-CN": + locale = zhCN; + break; + case "zh-HK": + locale = zhHK; + break; + default: + locale = enUS; + } + switch (source) { + case 'create': + return `${this.translate.instant("CREATED")}`; + case 'view': + return `${this.translate.instant("VIEWED")}`; + case 'scan': + return `${this.translate.instant("SCANNED")}`; + } + } + async presentToast(msg: string, duration: "short" | "long", pos: "top" | "center" | "bottom") { await Toast.show({ text: msg, @@ -237,14 +552,36 @@ export class SettingRecordPage { async presentLoading(msg: string): Promise { const loading = await this.loadingController.create({ - message: msg, - mode: "ios", - backdropDismiss: false + message: msg }); await loading.present(); return loading; } + async tapHaptic() { + if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) + } + } + + get color() { + switch (this.env.colorTheme) { + case 'dark': + return 'dark'; + case 'light': + return 'white'; + case 'black': + return 'black'; + default: + return 'white'; + } + } + get isIOS() { return this.platform.is('ios'); } diff --git a/src/app/pages/setting-result-buttons/setting-result-buttons-routing.module.ts b/src/app/pages/setting-result-buttons/setting-result-buttons-routing.module.ts new file mode 100644 index 0000000..69be198 --- /dev/null +++ b/src/app/pages/setting-result-buttons/setting-result-buttons-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingResultButtonsPage } from './setting-result-buttons.page'; + +const routes: Routes = [ + { + path: '', + component: SettingResultButtonsPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingResultButtonsPageRoutingModule {} diff --git a/src/app/pages/setting-result-buttons/setting-result-buttons.module.ts b/src/app/pages/setting-result-buttons/setting-result-buttons.module.ts new file mode 100644 index 0000000..1d3b404 --- /dev/null +++ b/src/app/pages/setting-result-buttons/setting-result-buttons.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingResultButtonsPageRoutingModule } from './setting-result-buttons-routing.module'; + +import { SettingResultButtonsPage } from './setting-result-buttons.page'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClient } from '@angular/common/http'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingResultButtonsPageRoutingModule + ], + declarations: [SettingResultButtonsPage] +}) +export class SettingResultButtonsPageModule {} diff --git a/src/app/pages/setting-result-buttons/setting-result-buttons.page.html b/src/app/pages/setting-result-buttons/setting-result-buttons.page.html new file mode 100644 index 0000000..f3db1fa --- /dev/null +++ b/src/app/pages/setting-result-buttons/setting-result-buttons.page.html @@ -0,0 +1,258 @@ + + + + + + + {{ 'TASK_BUTTON_LAYOUT' | translate }} + + + + + + +

+ +

+
+ +

+
+
+ + + + +

+ + {{ 'DETAILED' | translate }} + +

+
+ + +
+ + +

+ + {{ 'ICON_ONLY' | translate }} + +

+
+ + +
+
+ + + +

+ +

+
+ +

+
+
+ + + + + + + + + + + + + + + + + +

+ + {{ 'SEARCH' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'COPY_TEXT' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'BASE64' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'SHOW_QR_CODE' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'BOOKMARK' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'OPEN_URL' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'BROWSE_WEBSITE' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'ADD_CONTACT' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'CALL' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'SEND_MESSAGE' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'SEND_EMAIL' | translate }} + +

+
+ + +
+ + + + + +

+ + Open Food Facts + +

+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/pages/setting-result-buttons/setting-result-buttons.page.scss b/src/app/pages/setting-result-buttons/setting-result-buttons.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/setting-result-buttons/setting-result-buttons.page.ts b/src/app/pages/setting-result-buttons/setting-result-buttons.page.ts new file mode 100644 index 0000000..3aeee37 --- /dev/null +++ b/src/app/pages/setting-result-buttons/setting-result-buttons.page.ts @@ -0,0 +1,104 @@ +import { Component } from '@angular/core'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Preferences } from '@capacitor/preferences'; +import { Toast } from '@capacitor/toast'; +import { EnvService } from 'src/app/services/env.service'; + +@Component({ + selector: 'app-setting-result-buttons', + templateUrl: './setting-result-buttons.page.html', + styleUrls: ['./setting-result-buttons.page.scss'], +}) +export class SettingResultButtonsPage { + + constructor( + public env: EnvService, + ) { } + + async saveResultPageButtons() { + await Preferences.set({ key: this.env.KEY_RESULT_PAGE_BUTTONS, value: this.env.resultPageButtons }); + } + + async onSearchButtonChange(ev: any) { + this.env.showSearchButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_SEARCH_BUTTON, value: this.env.showSearchButton }); + await this.tapHaptic(); + } + + async onCopyButtonChange(ev: any) { + this.env.showCopyButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_COPY_BUTTON, value: this.env.showCopyButton }); + await this.tapHaptic(); + } + + async onBase64ButtonChange(ev: any) { + this.env.showBase64Button = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_BASE64_BUTTON, value: this.env.showBase64Button }); + await this.tapHaptic(); + } + + async onEnlargeButtonChange(ev: any) { + this.env.showEnlargeButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_ENLARGE_BUTTON, value: this.env.showEnlargeButton }); + await this.tapHaptic(); + } + + async onBookmarkButtonChange(ev: any) { + this.env.showBookmarkButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_BOOKMARK_BUTTON, value: this.env.showBookmarkButton }); + await this.tapHaptic(); + } + + async onOpenUrlButtonChange(ev: any) { + this.env.showOpenUrlButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_OPEN_URL_BUTTON, value: this.env.showOpenUrlButton }); + await this.tapHaptic(); + } + + async onBrowseButtonChange(ev: any) { + this.env.showBrowseButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_BROWSE_BUTTON, value: this.env.showBrowseButton }); + await this.tapHaptic(); + } + + async onAddContactButtonChange(ev: any) { + this.env.showAddContactButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_ADD_CONTACT_BUTTON, value: this.env.showAddContactButton }); + await this.tapHaptic(); + } + + async onCallButtonChange(ev: any) { + this.env.showCallButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_CALL_BUTTON, value: this.env.showCallButton }); + await this.tapHaptic(); + } + + async onSendMessageButtonChange(ev: any) { + this.env.showSendMessageButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_SEND_MESSAGE_BUTTON, value: this.env.showSendMessageButton }); + await this.tapHaptic(); + } + + async onSendEmailButtonChange(ev: any) { + this.env.showSendEmailButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_SEND_EMAIL_BUTTON, value: this.env.showSendEmailButton }); + await this.tapHaptic(); + } + + async onOpenFoodFactsButtonChange(ev: any) { + this.env.showOpenFoodFactsButton = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_SHOW_OPEN_FOOD_FACTS_BUTTON, value: this.env.showOpenFoodFactsButton }); + await this.tapHaptic(); + } + + async tapHaptic() { + if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) + } + } +} diff --git a/src/app/pages/setting-result/setting-result-routing.module.ts b/src/app/pages/setting-result/setting-result-routing.module.ts new file mode 100644 index 0000000..b91b6eb --- /dev/null +++ b/src/app/pages/setting-result/setting-result-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingResultPage } from './setting-result.page'; + +const routes: Routes = [ + { + path: '', + component: SettingResultPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingResultPageRoutingModule {} diff --git a/src/app/pages/setting-result/setting-result.module.ts b/src/app/pages/setting-result/setting-result.module.ts new file mode 100644 index 0000000..b7b66fb --- /dev/null +++ b/src/app/pages/setting-result/setting-result.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingResultPageRoutingModule } from './setting-result-routing.module'; + +import { SettingResultPage } from './setting-result.page'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClient } from '@angular/common/http'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingResultPageRoutingModule + ], + declarations: [SettingResultPage] +}) +export class SettingResultPageModule {} diff --git a/src/app/pages/setting-result/setting-result.page.html b/src/app/pages/setting-result/setting-result.page.html new file mode 100644 index 0000000..1eb4773 --- /dev/null +++ b/src/app/pages/setting-result/setting-result.page.html @@ -0,0 +1,102 @@ + + + + + + + {{ 'QR_CODE_AND_DECODED_RESULT' | translate }} + + + + + + {{ 'QR_CODE' | translate }} + + + + +

+ + {{ 'QR_CODE_STYLE' | translate }} + +

+
+
+ + + + +

+ + {{ 'AUTO_QR_CODE_POPUP' | translate }} + +

+
+
+ + + + +

+ + {{ 'AUTO_MAX_BRIGHTNESS' | translate }} + +

+
+
+ + + + +

+ + {{ 'AUTO_OPEN_URL' | translate }} + +

+
+
+ + {{ 'TASK' | translate }} + + + + +

+ + {{ 'TASK_BUTTON_LAYOUT' | translate }} + +

+
+
+ + + + + + + + + + + + + + + + +

+ + {{ 'SEARCH_ENGINE' | translate }} + +

+
+
+ +
\ No newline at end of file diff --git a/src/app/pages/setting-result/setting-result.page.scss b/src/app/pages/setting-result/setting-result.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/setting-result/setting-result.page.ts b/src/app/pages/setting-result/setting-result.page.ts new file mode 100644 index 0000000..1c6d264 --- /dev/null +++ b/src/app/pages/setting-result/setting-result.page.ts @@ -0,0 +1,40 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { EnvService } from 'src/app/services/env.service'; + +@Component({ + selector: 'app-setting-result', + templateUrl: './setting-result.page.html', + styleUrls: ['./setting-result.page.scss'], +}) +export class SettingResultPage { + + constructor( + public env: EnvService, + private router: Router, + ) { } + + setAutoQr() { + this.router.navigate(['setting-auto-qr']); + } + + setAutoMaxBrightness() { + this.router.navigate(['setting-auto-brightness']); + } + + setAutoOpenUrl() { + this.router.navigate(['setting-auto-open-url']); + } + + setQrStyle() { + this.router.navigate(['setting-qr']); + } + + setSearchEngine() { + this.router.navigate(['setting-search-engine']); + } + + setResultPageButtons() { + this.router.navigate(['setting-result-buttons']); + } +} diff --git a/src/app/pages/setting-search-engine/setting-search-engine.page.html b/src/app/pages/setting-search-engine/setting-search-engine.page.html index 02157c7..6d518b8 100644 --- a/src/app/pages/setting-search-engine/setting-search-engine.page.html +++ b/src/app/pages/setting-search-engine/setting-search-engine.page.html @@ -1,7 +1,7 @@ - - + + - + {{ 'SEARCH_ENGINE' | translate }} @@ -10,72 +10,108 @@ - - - - -

- - {{ 'GOOGLE' | translate }} - -

-
- - - -
- - - -

- - {{ 'BING' | translate }} - -

-
- - - -
- - - -

- - {{ 'YAHOO' | translate }} - -

-
- - - -
- - - -

- - {{ 'DUCK_DUCK_GO' | translate }} - -

-
- - - -
-
-

+

- {{ 'SEARCG_ENGINE_EXPLAIN' | translate }} +

+

-
+ + + + +

+ + {{ 'GOOGLE_SEARCH' | translate }} + +

+
+ + +
+ + + +

+ + {{ 'MICROSOFT_BING' | translate }} + +

+
+ + +
+ + + + +

+ + {{ 'YAHOO_SEARCH' | translate }} + +

+
+ + +
+ + + + +

+ + {{ 'DUCK_DUCK_GO' | translate }} + +

+
+ + +
+ + + + +

+ + {{ 'YANDEX' | translate }} + +

+
+ + +
+ + + + +

+ + {{ 'ECOSIA' | translate }} + +

+
+ + +
+ + + + +

+ + {{ 'BRAVE_SEARCH' | translate }} + +

+
+ + +
+
+ + \ No newline at end of file diff --git a/src/app/pages/setting-search-engine/setting-search-engine.page.ts b/src/app/pages/setting-search-engine/setting-search-engine.page.ts index fbb66b7..5bd4080 100644 --- a/src/app/pages/setting-search-engine/setting-search-engine.page.ts +++ b/src/app/pages/setting-search-engine/setting-search-engine.page.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { Preferences } from '@capacitor/preferences'; import { TranslateService } from '@ngx-translate/core'; import { EnvService } from 'src/app/services/env.service'; @@ -15,7 +16,7 @@ export class SettingSearchEnginePage { ) { } async saveSearchEngine() { - await this.env.storageSet("search-engine", this.env.searchEngine); + await Preferences.set({ key: this.env.KEY_SEARCH_ENGINE, value: this.env.searchEngine }); } diff --git a/src/app/pages/setting-start-page/setting-start-page-routing.module.ts b/src/app/pages/setting-start-page/setting-start-page-routing.module.ts new file mode 100644 index 0000000..90ea16d --- /dev/null +++ b/src/app/pages/setting-start-page/setting-start-page-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SettingStartPagePage } from './setting-start-page.page'; + +const routes: Routes = [ + { + path: '', + component: SettingStartPagePage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingStartPagePageRoutingModule {} diff --git a/src/app/pages/setting-start-page/setting-start-page.module.ts b/src/app/pages/setting-start-page/setting-start-page.module.ts new file mode 100644 index 0000000..380679c --- /dev/null +++ b/src/app/pages/setting-start-page/setting-start-page.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SettingStartPagePageRoutingModule } from './setting-start-page-routing.module'; + +import { SettingStartPagePage } from './setting-start-page.page'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClient } from '@angular/common/http'; + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + SettingStartPagePageRoutingModule + ], + declarations: [SettingStartPagePage] +}) +export class SettingStartPagePageModule {} diff --git a/src/app/pages/setting-start-page/setting-start-page.page.html b/src/app/pages/setting-start-page/setting-start-page.page.html new file mode 100644 index 0000000..ddab0cb --- /dev/null +++ b/src/app/pages/setting-start-page/setting-start-page.page.html @@ -0,0 +1,126 @@ + + + + + + + {{ 'APP_INITIAL_PAGE' | translate }} + + + + + + + +

+ +

+
+ +

+
+
+ + + + + + +

+ + {{ 'SCAN' | translate }} + +

+
+ + +
+ + + + + +

+ + {{ 'LOG' | translate }} + +

+
+
+ + + + +

+ + {{ 'BOOKMARK' | translate }} + +

+
+
+ + +
+ + + +

+ + {{ 'CREATE' | translate }} + +

+
+ + +
+ + + +

+ + {{ 'IMPORT' | translate }} + +

+
+ + +
+ + + +

+ + {{ 'SETTING' | translate }} + +

+
+ + +
+
+ + + +

+ +

+
+ +

+
+
+ + + +

+ + {{ (env.startPageHeader == 'on'? 'TURNED_ON' : 'TURNED_OFF') | translate }} + +

+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/pages/setting-start-page/setting-start-page.page.scss b/src/app/pages/setting-start-page/setting-start-page.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/setting-start-page/setting-start-page.page.ts b/src/app/pages/setting-start-page/setting-start-page.page.ts new file mode 100644 index 0000000..b37f81d --- /dev/null +++ b/src/app/pages/setting-start-page/setting-start-page.page.ts @@ -0,0 +1,41 @@ +import { Component } from '@angular/core'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Preferences } from '@capacitor/preferences'; +import { Toast } from '@capacitor/toast'; +import { EnvService } from 'src/app/services/env.service'; +import { fadeIn } from 'src/app/utils/animations'; + +@Component({ + selector: 'app-setting-start-page', + templateUrl: './setting-start-page.page.html', + styleUrls: ['./setting-start-page.page.scss'], + animations: [fadeIn] +}) +export class SettingStartPagePage { + + constructor( + public env: EnvService, + ) { } + + async saveStartPage() { + await Preferences.set({ key: this.env.KEY_START_PAGE, value: this.env.startPage }); + } + + async onStartPageHeaderChange(ev: any) { + this.env.startPageHeader = ev ? 'on' : 'off'; + await Preferences.set({ key: this.env.KEY_START_PAGE_HEADER, value: this.env.startPageHeader }); + await this.tapHaptic(); + } + + async tapHaptic() { + if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) + } + } + +} diff --git a/src/app/pages/setting-vibration/setting-vibration.page.html b/src/app/pages/setting-vibration/setting-vibration.page.html index 27a5324..b53bd6a 100644 --- a/src/app/pages/setting-vibration/setting-vibration.page.html +++ b/src/app/pages/setting-vibration/setting-vibration.page.html @@ -1,7 +1,7 @@ - - + + - + {{ 'VIBRATION' | translate }} @@ -10,52 +10,63 @@ + + +

+ +

+
+ +

+
+
+ - + -

+

{{ 'TURN_ON' | translate }}

-
- + -

+

- {{ 'TURN_ON_HAPTIC' | translate }} + {{ 'HAPTIC_FEEDBACK_ONLY' | translate }}

-
- + -

+

- {{ 'TURN_ON_SCANNED' | translate }} + {{ 'SCANNING_FEEDBACK_ONLY' | translate }}

-
- + -

+

{{ 'TURN_OFF' | translate }}

-
diff --git a/src/app/pages/setting-vibration/setting-vibration.page.ts b/src/app/pages/setting-vibration/setting-vibration.page.ts index a46bfef..0fe8f70 100644 --- a/src/app/pages/setting-vibration/setting-vibration.page.ts +++ b/src/app/pages/setting-vibration/setting-vibration.page.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { Preferences } from '@capacitor/preferences'; import { EnvService } from 'src/app/services/env.service'; @Component({ @@ -13,6 +14,6 @@ export class SettingVibrationPage { ) { } async saveVibration() { - await this.env.storageSet("vibration", this.env.vibration); + await Preferences.set({ key: this.env.KEY_VIBRATION, value: this.env.vibration }); } } diff --git a/src/app/pages/setting/setting.page.html b/src/app/pages/setting/setting.page.html index 5fcba65..48a8c9f 100644 --- a/src/app/pages/setting/setting.page.html +++ b/src/app/pages/setting/setting.page.html @@ -1,123 +1,181 @@ - - - - {{ 'SETTINGS' | translate }} - + +
+
+ + + {{ 'SIMPLE_QR' | translate}} + + + + + {{ 'SETTINGS' | translate }} + +
- - + +
+
+ +
+ +

+ + {{ 'ABOUT_SIMPLE_QR' | translate }} + +

+
+
+
+ + {{ 'FUNCTIONS' | translate }} + + -

+

- {{ 'ABOUT' | translate }} + {{ 'QR_CODE_AND_DECODED_RESULT' | translate }}

- - + + -

+

+ + {{ 'LOG_BACKUP_AND_RESTORE' | translate }} + +

+
+
+ + {{ 'APPEARANCE_AND_EFFECTS' | translate }} + + + + + +

{{ 'LANGUAGE' | translate }}

-
- + -

+

{{ 'COLOR_THEME' | translate }}

-
- - - - - + + + -

+

- {{ 'SEARCH_ENGINE' | translate }} + {{ 'SCREEN_ORIENTATION' | translate }}

- + -

+

{{ 'VIBRATION' | translate }}

- - + + {{ 'OTHERS' | translate }} + + -

+

- {{ 'LOGGING_BACKUP_RESTORE' | translate }} - -

-
- -
- - - - -

- - {{ 'DEBUG_MODE' | translate }} + {{ 'APP_INITIAL_PAGE' | translate }}

- + + + + +

+ + {{ 'AUTO_KILL_BACKGROUND' | translate }} + +

+
+
+ -

+

{{ 'RESET_APP' | translate }}

-
- + + + +

+ + {{ 'RATE_THE_APP' | translate }} + +

+
+
+ + + +

+ + {{ 'RATE_THE_APP' | translate }} + +

+
+
+ -

+

{{ 'EXIT_APP' | translate }}

- + + + + +

+ + {{ 'DEBUG_MODE' | translate }} + +

+
+
\ No newline at end of file diff --git a/src/app/pages/setting/setting.page.ts b/src/app/pages/setting/setting.page.ts index 76f5ec3..5b3cb0a 100644 --- a/src/app/pages/setting/setting.page.ts +++ b/src/app/pages/setting/setting.page.ts @@ -1,10 +1,10 @@ import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { Toast } from '@capacitor/toast'; -import { AppVersion } from '@awesome-cordova-plugins/app-version/ngx'; -import { AlertController, LoadingController, Platform, ToastController } from '@ionic/angular'; +import { AlertController, LoadingController, Platform } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; import { EnvService } from 'src/app/services/env.service'; +import { SplashScreen } from '@capacitor/splash-screen'; @Component({ selector: 'app-setting', @@ -19,16 +19,31 @@ export class SettingPage { private router: Router, public env: EnvService, public translate: TranslateService, - public appVersion: AppVersion, private platform: Platform, - ) { - + ) { + + } + + async ionViewDidEnter() { + await SplashScreen.hide() + } + + get isIos(): boolean { + return this.platform.is('ios'); } get isAndroid(): boolean { return this.platform.is('android'); } + rateAndroidApp() { + window.open(this.env.GOOGLE_PLAY_URL, '_system'); + } + + rateIosApp() { + window.open(this.env.APP_STORE_URL, '_system'); + } + setLanguage() { this.router.navigate(['setting-language']); } @@ -37,6 +52,10 @@ export class SettingPage { this.router.navigate(['setting-color']); } + setOrientation() { + this.router.navigate(['setting-orientation']); + } + setVibration() { this.router.navigate(['setting-vibration']); } @@ -49,8 +68,12 @@ export class SettingPage { this.router.navigate(['backup-restore']); } - setSearchEngine() { - this.router.navigate(['setting-search-engine']); + setStartPage() { + this.router.navigate(['setting-start-page']); + } + + setResult() { + this.router.navigate(['setting-result']); } setDebugMode() { @@ -61,6 +84,10 @@ export class SettingPage { this.router.navigate(['about']); } + setAutoCloseApp() { + this.router.navigate(['setting-auto-exit']); + } + async resetApp() { const alert = await this.alertController.create({ header: this.translate.instant('RESET_APP'), @@ -68,8 +95,9 @@ export class SettingPage { cssClass: ['alert-bg'], buttons: [ { - text: this.translate.instant('YES'), + text: this.translate.instant('FULL_RESET'), handler: async () => { + const loading = await this.presentLoading(this.translate.instant("PLEASE_WAIT")); await window.caches.keys().then( keys => { keys.forEach( @@ -80,10 +108,30 @@ export class SettingPage { } ); await this.env.resetAll(); + loading.dismiss(); + this.presentToast(this.translate.instant("DONE"), "short", "bottom"); } }, { - text: this.translate.instant('NO'), + text: this.translate.instant('ONLY_DELETE_DATA'), + handler: async () => { + const loading = await this.presentLoading(this.translate.instant("PLEASE_WAIT")); + await this.env.resetData(); + loading.dismiss(); + this.presentToast(this.translate.instant("DONE"), "short", "bottom"); + } + }, + { + text: this.translate.instant('ONLY_RESET_SETTING'), + handler: async () => { + const loading = await this.presentLoading(this.translate.instant("PLEASE_WAIT")); + await this.env.resetSetting(); + loading.dismiss(); + this.presentToast(this.translate.instant("DONE"), "short", "bottom"); + } + }, + { + text: this.translate.instant('CANCEL'), role: 'cancel' }, ] @@ -91,26 +139,12 @@ export class SettingPage { alert.present(); } - async confirmExitApp(): Promise { - const alert = await this.alertController.create({ - header: this.translate.instant('EXIT_APP'), - message: this.translate.instant('MSG.EXIT_APP'), - cssClass: ['alert-bg'], - buttons: [ - { - text: this.translate.instant('YES'), - handler: () => { - navigator['app'].exitApp(); - } - }, - { - text: this.translate.instant('NO'), - role: 'cancel', - cssClass: 'btn-inverse' - } - ] - }); - await alert.present(); + exitApp() { + navigator['app'].exitApp(); + } + + openGooglePlay(): void { + window.open(this.env.GOOGLE_PLAY_URL, '_system'); } async presentToast(msg: string, duration: "short" | "long", pos: "top" | "center" | "bottom") { @@ -120,4 +154,12 @@ export class SettingPage { position: pos }); } + + async presentLoading(msg: string): Promise { + const loading = await this.loadingController.create({ + message: msg + }); + await loading.present(); + return loading; + } } diff --git a/src/app/pages/tabs/tabs-routing.module.ts b/src/app/pages/tabs/tabs-routing.module.ts index a0f6f1b..128cd8e 100644 --- a/src/app/pages/tabs/tabs-routing.module.ts +++ b/src/app/pages/tabs/tabs-routing.module.ts @@ -7,40 +7,51 @@ const routes: Routes = [ path: 'tabs', component: TabsPage, children: [ + { + path: '', + redirectTo: '/tabs/landing', + pathMatch: 'full' + }, + { + path: 'landing', + data: { preload: true }, + loadChildren: () => import('../landing/landing.module').then(m => m.LandingPageModule) + }, { path: 'scan', + data: { preload: true }, loadChildren: () => import('../scan/scan.module').then(m => m.ScanPageModule) }, { path: 'result', - loadChildren: () => import('../result/result.module').then( m => m.ResultPageModule) + data: { preload: true }, + loadChildren: () => import('../result/result.module').then(m => m.ResultPageModule) }, { path: 'history', - loadChildren: () => import('../history/history.module').then( m => m.HistoryPageModule) + data: { preload: true }, + loadChildren: () => import('../history/history.module').then(m => m.HistoryPageModule) }, { path: 'setting', - loadChildren: () => import('../setting/setting.module').then( m => m.SettingPageModule) + data: { preload: true }, + loadChildren: () => import('../setting/setting.module').then(m => m.SettingPageModule) }, { path: 'generate', - loadChildren: () => import('../generate/generate.module').then( m => m.GeneratePageModule) + data: { preload: true }, + loadChildren: () => import('../generate/generate.module').then(m => m.GeneratePageModule) }, { path: 'import-image', - loadChildren: () => import('../import-image/import-image.module').then( m => m.ImportImagePageModule) - }, - { - path: '', - redirectTo: '/tabs/scan', - pathMatch: 'full' + data: { preload: true }, + loadChildren: () => import('../import-image/import-image.module').then(m => m.ImportImagePageModule) } ] }, { path: '', - redirectTo: '/tabs/scan', + redirectTo: '/tabs/landing', pathMatch: 'full' } ]; diff --git a/src/app/pages/tabs/tabs.page.html b/src/app/pages/tabs/tabs.page.html index 0f18e19..9e9f8b2 100644 --- a/src/app/pages/tabs/tabs.page.html +++ b/src/app/pages/tabs/tabs.page.html @@ -1,29 +1,29 @@ - + - {{ 'CREATE' | translate }} - + - {{ 'IMPORT' | translate }} - + - {{ 'SCAN' | translate }} - - - {{ 'HISTORY' | translate }} + + + + + + + - + - {{ 'SETTING' | translate }} diff --git a/src/app/pages/tabs/tabs.page.ts b/src/app/pages/tabs/tabs.page.ts index 23f4960..ac968e4 100644 --- a/src/app/pages/tabs/tabs.page.ts +++ b/src/app/pages/tabs/tabs.page.ts @@ -1,5 +1,9 @@ import { Component } from '@angular/core'; +import { Router } from '@angular/router'; import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Preferences } from '@capacitor/preferences'; +import { SplashScreen } from '@capacitor/splash-screen'; +import { Toast } from '@capacitor/toast'; import { AlertController, Platform } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; import { EnvService } from 'src/app/services/env.service'; @@ -10,17 +14,134 @@ import { EnvService } from 'src/app/services/env.service'; styleUrls: ['tabs.page.scss'] }) export class TabsPage { - routerOutlet: any; + + exitAppTimeout: NodeJS.Timeout; constructor( - private env: EnvService, + private translate: TranslateService, + public env: EnvService, + private platform: Platform, + private router: Router, + private alertController: AlertController, ) { - + this.platform.pause.subscribe( + _ => { + if (this.platform.is('android')) { + if (this.env.autoExitAppMin != -1) { + this.exitAppTimeout = setTimeout( + () => { + if (this.env.isDebugging) { + this.presentToast("App will be killed!", "short", "bottom"); + } + navigator['app'].exitApp(); + }, this.env.autoExitAppMin * 60 * 1000 + ) + if (this.env.isDebugging) { + this.presentToast("App will be destroyed after " + this.env.autoExitAppMin + " minutes", "short", "bottom"); + } + } else { + if (this.env.isDebugging) { + this.presentToast("App won't be destroyed by itself", "short", "bottom"); + } + } + }; + } + ) + this.platform.resume.subscribe( + _ => { + if (this.exitAppTimeout != null) { + clearTimeout(this.exitAppTimeout); + delete this.exitAppTimeout; + if (this.env.isDebugging) { + this.presentToast("Cleared Exit App Timeout", "short", "bottom"); + } + } + } + ) + } + + async ionViewDidEnter() { + if (this.env.firstAppLoad) { + this.env.firstAppLoad = false; + this.env.initObservable.subscribe(async value => { + console.log(`tabs.page.ts - ionViewDidEnter() - initObservable value: ${value}`) + if (value) { + console.log(`tabs.page.ts - ionViewDidEnter() - env.startPage: ${this.env.startPage}`) + await this.router.navigate([this.env.startPage], { replaceUrl: true }); + await this.loadPatchNote(); + } + }); + } + } + + async loadPatchNote() { + const storageKey = this.platform.is('ios') ? this.env.KEY_IOS_NOT_SHOW_UPDATE_NOTES : this.env.KEY_ANDROID_NOT_SHOW_UPDATE_NOTES; + await Preferences.get({ key: storageKey }).then( + async result => { + if (result.value != null) { + this.env.notShowUpdateNotes = result.value == 'yes'; + } else { + this.env.notShowUpdateNotes = false; + } + await Preferences.set({ key: storageKey, value: 'yes' }); + if (!this.env.notShowUpdateNotes) { + this.env.notShowUpdateNotes = true; + const versionWording = this.translate.instant("VERSION_VERSION") as string; + await this.presentToast(versionWording.replace("{version}", this.env.appVersionNumber), "short", 'bottom'); + } + } + ) + } + + async showUpdateNotes() { + const alert = await this.alertController.create({ + header: this.translate.instant("UPDATE_SUCCESSFULLY"), + message: this.platform.is('ios') ? this.translate.instant("UPDATE.UPDATE_NOTES_IOS") : this.translate.instant("UPDATE.UPDATE_NOTES_ANDROID"), + buttons: [ + { + text: this.translate.instant("CLOSE"), + handler: () => true, + }, + { + text: this.translate.instant("VIEW_GITHUB"), + handler: () => { + this.openGitHubRelease(); + } + } + ], + cssClass: ['left-align', 'alert-bg'] + }); + await alert.present(); + } + + openGooglePlay(): void { + window.open(this.env.GOOGLE_PLAY_URL, '_system'); + } + + openAppStore(): void { + window.open(this.env.APP_STORE_URL, '_system'); + } + + openGitHubRelease() { + window.open(this.env.GITHUB_RELEASE_URL, '_system'); + } + + async presentToast(msg: string, duration: "short" | "long", pos: "top" | "center" | "bottom") { + await Toast.show({ + text: msg, + duration: duration, + position: pos + }); } async tapHaptic() { if (this.env.vibration === 'on' || this.env.vibration === 'on-haptic') { - await Haptics.impact({ style: ImpactStyle.Medium }); + await Haptics.impact({ style: ImpactStyle.Light }) + .catch(async err => { + if (this.env.debugMode === 'on') { + await Toast.show({ text: 'Err when Haptics.impact: ' + JSON.stringify(err), position: "top", duration: "long" }) + } + }) } } } diff --git a/src/app/services/custom-preloading-strategy.service.ts b/src/app/services/custom-preloading-strategy.service.ts new file mode 100644 index 0000000..40adab8 --- /dev/null +++ b/src/app/services/custom-preloading-strategy.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { PreloadingStrategy, Route } from '@angular/router'; +import { Observable, of } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class CustomPreloadingStrategyService implements PreloadingStrategy { + + preload(route: Route, fn: () => Observable): Observable { + if (route.data && route.data.preload) { + return fn(); + } + return of(null); + } + +} diff --git a/src/app/services/env.service.ts b/src/app/services/env.service.ts index 3a35c2a..1c24c57 100644 --- a/src/app/services/env.service.ts +++ b/src/app/services/env.service.ts @@ -1,235 +1,1419 @@ import { OverlayContainer } from '@angular/cdk/overlay'; import { Injectable } from '@angular/core'; -import { AppVersion } from '@awesome-cordova-plugins/app-version/ngx'; import { Device, DeviceInfo } from '@capacitor/device'; import { ThemeDetection, ThemeDetectionResponse } from '@awesome-cordova-plugins/theme-detection/ngx'; -import { Platform } from '@ionic/angular'; +import { ScreenOrientation } from '@awesome-cordova-plugins/screen-orientation/ngx'; +import { LoadingController, Platform } from '@ionic/angular'; import { Storage } from '@ionic/storage-angular'; import { TranslateService } from '@ngx-translate/core'; -import * as moment from 'moment'; +import { format } from 'date-fns'; import { environment } from 'src/environments/environment'; import { Bookmark } from '../models/bookmark'; import { ScanRecord } from '../models/scan-record'; +import { Toast } from '@capacitor/toast'; +import { v4 as uuidv4 } from 'uuid'; +import { Preferences } from '@capacitor/preferences'; +import { Observable } from 'rxjs'; + +export declare type LanguageType = 'de' | 'en' | 'fr' | 'it' | 'pt-BR' | 'ru' | 'zh-CN' | 'zh-HK'; +export declare type TabPageType = "/tabs/scan" | "/tabs/generate" | "/tabs/import-image" | "/tabs/history" | "/tabs/setting"; +export declare type HistoryPageSegmentType = 'history' | 'bookmarks'; +export declare type OnOffType = "on" | "off"; +export declare type ColorThemeType = 'light' | 'dark' | 'black'; +export declare type ErrorCorrectionLevelType = 'L' | 'M' | 'Q' | 'H'; +export declare type VibrationType = "on" | "off" | 'on-haptic' | 'on-scanned'; +export declare type OrientationType = 'portrait' | 'landscape'; +export declare type SearchEngineType = 'google' | 'bing' | 'yahoo' | 'duckduckgo' | 'yandex' | 'ecosia' | 'brave'; +export declare type ResultPageButtonsType = 'detailed' | 'icon-only'; +export declare type QrResultContentTypeType = "freeText" | "url" | "contact" | "phone" | "sms" | "emailW3C" | "emailDocomo" | "wifi" | "geo"; +export declare type QrCreateContentTypeType = "freeText" | "url" | "contact" | "phone" | "sms" | "emailW3C" | "emailDocomo" | "wifi" | "geo"; @Injectable({ providedIn: 'root' }) export class EnvService { - public appVersionNumber: string = '1.0.0'; + public appVersionNumber: string = '4.1.0'; - public languages: string[] = ['en', 'zh-HK', 'zh-CN']; - public language: 'en' | 'zh-HK' | 'zh-CN' = 'en'; - public selectedLanguage: 'default' | 'en' | 'zh-HK' | 'zh-CN' = 'default'; - public colorTheme: 'light' | 'dark' | 'black' = 'light'; - public selectedColorTheme: 'default' | 'light' | 'dark' | 'black' = 'default'; - public scanRecordLogging: 'on' | 'off' = 'on'; - public vibration: 'on' | 'on-haptic' | 'on-scanned' | 'off' = 'on'; - public notShowHistoryTutorial: boolean = false; + public startPage: TabPageType = "/tabs/scan"; + public historyPageStartSegment: HistoryPageSegmentType = 'history'; + public startPageHeader: OnOffType = 'on'; + public languages: LanguageType[] = ['en', 'zh-HK', 'zh-CN', 'de', 'fr', 'it', 'pt-BR', 'ru']; + public language: LanguageType = 'en'; + public selectedLanguage: 'default' | LanguageType = 'default'; + public colorTheme: ColorThemeType = 'light'; + public selectedColorTheme: 'default' | ColorThemeType = 'default'; + public scanRecordLogging: OnOffType = 'on'; + public recordsLimit: 30 | 50 | 100 | -1 = -1; + public showNumberOfRecords: OnOffType = 'on'; + public autoMaxBrightness: OnOffType = 'off'; + public autoOpenUrl: OnOffType = 'off'; + public errorCorrectionLevel: ErrorCorrectionLevelType = 'M'; + public qrCodeLightR: number = 255; + public qrCodeLightG: number = 255; + public qrCodeLightB: number = 255; + public qrCodeDarkR: number = 34; + public qrCodeDarkG: number = 36; + public qrCodeDarkB: number = 40; + public qrCodeMargin: number = 3; + public vibration: VibrationType = 'on'; + public orientation: 'default' | OrientationType = 'default'; public notShowUpdateNotes: boolean = false; - public searchEngine: 'google' | 'bing' | 'yahoo' | 'duckduckgo' = 'google'; - public debugModeOn: 'on' | 'off' = 'off'; + public searchEngine: SearchEngineType = 'google'; + public resultPageButtons: ResultPageButtonsType = 'detailed'; + public showQrAfterCameraScan: OnOffType = 'off'; + public showQrAfterImageScan: OnOffType = 'off'; + public showQrAfterCreate: OnOffType = 'on'; + public showQrAfterLogView: OnOffType = 'on'; + public showQrAfterBookmarkView: OnOffType = 'on'; + public showSearchButton: OnOffType = 'on'; + public showCopyButton: OnOffType = 'on'; + public showBase64Button: OnOffType = 'on'; + public showEnlargeButton: OnOffType = 'on'; + public showBookmarkButton: OnOffType = 'on'; + public showOpenUrlButton: OnOffType = 'on'; + public showBrowseButton: OnOffType = 'on'; + public showAddContactButton: OnOffType = 'on'; + public showCallButton: OnOffType = 'on'; + public showSendMessageButton: OnOffType = 'on'; + public showSendEmailButton: OnOffType = 'on'; + public showOpenFoodFactsButton: OnOffType = 'on'; + public showExitAppAlert: OnOffType = "on"; + public debugMode: OnOffType = 'off'; + public autoExitAppMin: 1 | 3 | 5 | -1 = -1; + + public readonly KEY_START_PAGE = "start-page"; + public readonly KEY_HISTORY_PAGE_START_SEGMENT = "history-page-start-segment"; + public readonly KEY_START_PAGE_HEADER = "start-page-header"; + public readonly KEY_SCAN_RECORDS = "scanRecords"; + public readonly KEY_BOOKMARKS = "bookmarks"; + public readonly KEY_LANGUAGE = "language"; + public readonly KEY_COLOR = "color"; + public readonly KEY_DEBUG_MODE = "debug-mode-on"; + public readonly KEY_SHOW_EXIT_APP_ALERT = "showExitAppAlert"; + public readonly KEY_ORIENTATION = "orientation"; + public readonly KEY_SCAN_RECORD_LOGGING = "scan-record-logging"; + public readonly KEY_RECORDS_LIMIT = "recordsLimit"; + public readonly KEY_SHOW_NUMBER_OF_RECORDS = "showNumberOfRecords"; + public readonly KEY_VIBRATION = "vibration"; + public readonly KEY_ERROR_CORRECTION_LEVEL = "error-correction-level"; + public readonly KEY_QR_CODE_LIGHT_R = "qrCodeLightR"; + public readonly KEY_QR_CODE_LIGHT_G = "qrCodeLightG"; + public readonly KEY_QR_CODE_LIGHT_B = "qrCodeLightB"; + public readonly KEY_QR_CODE_DARK_R = "qrCodeDarkR"; + public readonly KEY_QR_CODE_DARK_G = "qrCodeDarkG"; + public readonly KEY_QR_CODE_DARK_B = "qrCodeDarkB"; + public readonly KEY_QR_CODE_MARGIN = "qrCodeMargin"; + public readonly KEY_AUTO_MAX_BRIGHTNESS = "auto-max-brightness"; + public readonly KEY_AUTO_OPEN_URL = "auto-open-url"; + public readonly KEY_SEARCH_ENGINE = "search-engine"; + public readonly KEY_RESULT_PAGE_BUTTONS = "result-page-buttons"; + public readonly KEY_SHOW_QR_AFTER_CAMERA_SCAN = "show-qr-after-camera-scan"; + public readonly KEY_SHOW_QR_AFTER_IMAGE_SCAN = "show-qr-after-image-scan"; + public readonly KEY_SHOW_QR_AFTER_CREATE = "show-qr-after-create"; + public readonly KEY_SHOW_QR_AFTER_LOG_VIEW = "show-qr-after-log-view"; + public readonly KEY_SHOW_QR_AFTER_BOOKMARK_VIEW = "show-qr-after-bookmark-view"; + public readonly KEY_SHOW_SEARCH_BUTTON = "showSearchButton"; + public readonly KEY_SHOW_COPY_BUTTON = "showCopyButton"; + public readonly KEY_SHOW_BASE64_BUTTON = "showBase64Button"; + public readonly KEY_SHOW_ENLARGE_BUTTON = "showEnlargeButton"; + public readonly KEY_SHOW_BOOKMARK_BUTTON = "showBookmarkButton"; + public readonly KEY_SHOW_OPEN_URL_BUTTON = "showOpenUrlButton"; + public readonly KEY_SHOW_BROWSE_BUTTON = "showBrowseButton"; + public readonly KEY_SHOW_ADD_CONTACT_BUTTON = "showAddContactButton"; + public readonly KEY_SHOW_CALL_BUTTON = "showCallButton"; + public readonly KEY_SHOW_SEND_MESSAGE_BUTTON = "showSendMessageButton"; + public readonly KEY_SHOW_SEND_EMAIL_BUTTON = "showSendEmailButton"; + public readonly KEY_SHOW_OPEN_FOOD_FACTS_BUTTON = "showOpenFoodFactsButton"; + public readonly KEY_AUTO_EXIT_MIN = "autoExitAppMin"; + + public readonly KEY_ANDROID_NOT_SHOW_UPDATE_NOTES = "not-show-update-notes-v40100"; + public readonly KEY_IOS_NOT_SHOW_UPDATE_NOTES = "not-show-update-notes-v40100"; + public readonly KEY_ANDROID_PREV_NOT_SHOW_UPDATE_NOTES = "not-show-update-notes-v40001"; + public readonly KEY_IOS_PREV_NOT_SHOW_UPDATE_NOTES = "not-show-update-notes-v40001"; public readonly APP_FOLDER_NAME: string = 'SimpleQR'; + public readonly GOOGLE_SEARCH_URL: string = "https://www.google.com/search?q="; public readonly BING_SEARCH_URL: string = "https://www.bing.com/search?q="; public readonly YAHOO_SEARCH_URL: string = "https://search.yahoo.com/search?p="; public readonly DUCK_DUCK_GO_SEARCH_URL: string = "https://duckduckgo.com/?q="; + public readonly YANDEX_SEARCH_URL: string = "https://yandex.com/search/?text="; + public readonly ECOSIA_SEARCH_URL: string = "https://www.ecosia.org/search?method=index&q="; + public readonly BRAVE_SEARCH_URL: string = "https://search.brave.com/search?q="; + public readonly GITHUB_REPO_URL: string = "https://github.com/tomfong/simple-qr"; public readonly GOOGLE_PLAY_URL: string = "https://play.google.com/store/apps/details?id=com.tomfong.simpleqr"; public readonly APP_STORE_URL: string = "https://apps.apple.com/us/app/simple-qr-by-tom-fong/id1621121553"; + public readonly GITHUB_RELEASE_URL: string = "https://github.com/tomfong/simple-qr/releases"; public readonly PRIVACY_POLICY: string = "https://www.privacypolicies.com/live/771b1123-99bb-4bfe-815e-1046c0437a0f"; - public readonly PREV_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v20300"; - public readonly PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v20301"; - private _storage: Storage | null = null; - private _scannedData: string = ''; - private _scannedDataFormat: string = ''; - private _scanRecords: ScanRecord[] = []; - private _bookmarks: Bookmark[] = []; + resultContent: string = ''; + editingContent: boolean = false; + resultContentFormat: string = ''; + scanRecords: ScanRecord[] = []; + bookmarks: Bookmark[] = []; + viewingScanRecords: ScanRecord[] = []; + viewingBookmarks: Bookmark[] = []; private _deviceInfo: DeviceInfo | undefined = undefined; + recordSource: 'create' | 'view' | 'scan'; + detailedRecordSource: 'create' | 'view-log' | 'view-bookmark' | 'scan-camera' | 'scan-image'; + viewResultFrom: '/tabs/scan' | '/tabs/import-image' | '/tabs/generate' | '/tabs/history'; + + public firstAppLoad: boolean = true; // once loaded, turn it false + + initObservable: Observable; + constructor( private platform: Platform, - private storage: Storage, + private ionicStorage: Storage, public translate: TranslateService, private overlayContainer: OverlayContainer, private themeDetection: ThemeDetection, - private appVersion: AppVersion, + private screenOrientation: ScreenOrientation, ) { this.platform.ready().then( - async () => { - await this.init(); + async _ => { + this.initObservable = new Observable(subs => { + new Promise(async _ => { + await Device.getInfo().then( + value => { + this._deviceInfo = value; + } + ); + await this._transferStorage(); + console.log(`env.service.ts - constructor - _transferStorage()`) + await this._loadStorage(); + console.log(`env.service.ts - constructor - _loadStorage()`) + subs.next(true); + }); + }); } ) } - private async init() { - this._deviceInfo = await Device.getInfo(); - this.appVersionNumber = await this.appVersion.getVersionNumber(); - const storage = await this.storage.create(); - this._storage = storage; - this._storage.remove(this.PREV_PATCH_NOTE_STORAGE_KEY).catch(err => {}); - this.storageGet("language").then( - async value => { - if (value !== null && value !== undefined) { - this.selectedLanguage = value; + private async _transferStorage() { + const oldStorage = await this.ionicStorage.create(); + const length = await oldStorage.length(); + if (length > 0) { + await oldStorage.get(this.KEY_LANGUAGE).then( + async value => { + if (value != null) { + this.selectedLanguage = value; + } else { + this.selectedLanguage = 'default'; + } + this.toggleLanguageChange(); + await Preferences.set({ + key: this.KEY_LANGUAGE, + value: this.selectedLanguage, + }); + } + ); + await this.presentToast(this.translate.instant("OPTIMIZING_DATA_..."), "short", "bottom"); + await oldStorage.get(this.KEY_START_PAGE).then( + async value => { + if (value != null) { + this.startPage = value; + } else { + this.startPage = '/tabs/scan'; + } + await Preferences.set({ + key: this.KEY_START_PAGE, + value: this.startPage, + }); + } + ); + await oldStorage.get(this.KEY_HISTORY_PAGE_START_SEGMENT).then( + async value => { + if (value != null) { + this.historyPageStartSegment = value; + } else { + this.historyPageStartSegment = 'history'; + } + await Preferences.set({ + key: this.KEY_HISTORY_PAGE_START_SEGMENT, + value: this.historyPageStartSegment, + }); + } + ); + await oldStorage.get(this.KEY_START_PAGE_HEADER).then( + async value => { + if (value != null) { + this.startPageHeader = value; + } else { + this.startPageHeader = 'on'; + } + await Preferences.set({ + key: this.KEY_START_PAGE_HEADER, + value: this.startPageHeader, + }); + } + ); + await oldStorage.get("RZUeHwaYWGkiNhsb5nld7vdDYE7pzRyB").then( + async value => { + if (value != null) { + try { + this.scanRecords = JSON.parse(value); + this.scanRecords.forEach( + r => { + const tCreatedAt = r.createdAt; + r.createdAt = new Date(tCreatedAt); + } + ); + this.scanRecords.sort((r1, r2) => { + return r2.createdAt.getTime() - r1.createdAt.getTime(); + }); + } catch (err) { + console.error(err); + this.scanRecords = []; + } + } + await Preferences.set({ + key: this.KEY_SCAN_RECORDS, + value: JSON.stringify(this.scanRecords), + }); + } + ); + await oldStorage.get("lB9STlXHpk7G8STLcJZNreiIxeFWPxPS").then( + async value => { + if (value != null) { + try { + this.bookmarks = JSON.parse(value); + this.bookmarks.forEach( + b => { + if (b.id == null) { + b.id = uuidv4(); + } + const tCreatedAt = b.createdAt; + b.createdAt = new Date(tCreatedAt); + } + ); + this.bookmarks.sort((a, b) => { + return ('' + a.tag ?? '').localeCompare(b.tag ?? ''); + }); + } catch (err) { + console.error(err); + this.bookmarks = []; + } + } + await Preferences.set({ + key: this.KEY_BOOKMARKS, + value: JSON.stringify(this.bookmarks), + }); + } + ) + await oldStorage.get(this.KEY_COLOR).then( + async value => { + if (value != null) { + this.selectedColorTheme = value; + } else { + this.selectedColorTheme = 'default'; + } + await this.toggleColorTheme(); + await Preferences.set({ + key: this.KEY_COLOR, + value: this.selectedColorTheme, + }); + } + ); + await oldStorage.get(this.KEY_DEBUG_MODE).then( + async value => { + if (value != null) { + this.debugMode = value; + } else { + this.debugMode = 'off'; + } + await Preferences.set({ + key: this.KEY_DEBUG_MODE, + value: this.debugMode, + }); + } + ); + await oldStorage.get(this.KEY_ORIENTATION).then( + async value => { + if (value != null) { + this.orientation = value; + } else { + this.orientation = 'default'; + } + await this.toggleOrientationChange(); + await Preferences.set({ + key: this.KEY_ORIENTATION, + value: this.orientation, + }); + } + ); + await oldStorage.get(this.KEY_SCAN_RECORD_LOGGING).then( + async value => { + if (value != null) { + this.scanRecordLogging = value; + } else { + this.scanRecordLogging = 'on'; + } + await Preferences.set({ + key: this.KEY_SCAN_RECORD_LOGGING, + value: this.scanRecordLogging, + }); + } + ); + await oldStorage.get(this.KEY_RECORDS_LIMIT).then( + async value => { + if (value != null) { + this.recordsLimit = value; + } else { + this.recordsLimit = -1; + } + await Preferences.set({ + key: this.KEY_RECORDS_LIMIT, + value: JSON.stringify(this.recordsLimit), + }); + } + ); + await oldStorage.get(this.KEY_SHOW_NUMBER_OF_RECORDS).then( + async value => { + if (value != null) { + this.showNumberOfRecords = value; + } else { + this.showNumberOfRecords = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_NUMBER_OF_RECORDS, + value: this.showNumberOfRecords, + }); + } + ); + await oldStorage.get(this.KEY_VIBRATION).then( + async value => { + if (value != null) { + this.vibration = value; + } else { + this.vibration = 'on'; + } + await Preferences.set({ + key: this.KEY_VIBRATION, + value: this.vibration, + }); + } + ); + await oldStorage.get(this.KEY_ERROR_CORRECTION_LEVEL).then( + async value => { + if (value != null) { + this.errorCorrectionLevel = value; + } else { + this.errorCorrectionLevel = 'M'; + } + await Preferences.set({ + key: this.KEY_ERROR_CORRECTION_LEVEL, + value: this.errorCorrectionLevel, + }); + } + ); + await oldStorage.get(this.KEY_QR_CODE_LIGHT_R).then( + async value => { + if (value != null) { + this.qrCodeLightR = value; + } else { + this.qrCodeLightR = 255; + } + await Preferences.set({ + key: this.KEY_QR_CODE_LIGHT_R, + value: JSON.stringify(this.qrCodeLightR), + }); + } + ); + await oldStorage.get(this.KEY_QR_CODE_LIGHT_G).then( + async value => { + if (value != null) { + this.qrCodeLightG = value; + } else { + this.qrCodeLightG = 255; + } + await Preferences.set({ + key: this.KEY_QR_CODE_LIGHT_G, + value: JSON.stringify(this.qrCodeLightG), + }); + } + ); + await oldStorage.get(this.KEY_QR_CODE_LIGHT_B).then( + async value => { + if (value != null) { + this.qrCodeLightB = value; + } else { + this.qrCodeLightB = 255; + } + await Preferences.set({ + key: this.KEY_QR_CODE_LIGHT_B, + value: JSON.stringify(this.qrCodeLightB), + }); + } + ); + await oldStorage.get(this.KEY_QR_CODE_DARK_R).then( + async value => { + if (value != null) { + this.qrCodeDarkR = value; + } else { + this.qrCodeDarkR = 34; + } + await Preferences.set({ + key: this.KEY_QR_CODE_DARK_R, + value: JSON.stringify(this.qrCodeDarkR), + }); + } + ); + await oldStorage.get(this.KEY_QR_CODE_DARK_G).then( + async value => { + if (value != null) { + this.qrCodeDarkG = value; + } else { + this.qrCodeDarkG = 36; + } + await Preferences.set({ + key: this.KEY_QR_CODE_DARK_G, + value: JSON.stringify(this.qrCodeDarkG), + }); + } + ); + await oldStorage.get(this.KEY_QR_CODE_DARK_B).then( + async value => { + if (value != null) { + this.qrCodeDarkB = value; + } else { + this.qrCodeDarkB = 40; + } + await Preferences.set({ + key: this.KEY_QR_CODE_DARK_B, + value: JSON.stringify(this.qrCodeDarkB), + }); + } + ); + await oldStorage.get(this.KEY_QR_CODE_MARGIN).then( + async value => { + if (value != null) { + this.qrCodeMargin = value; + } else { + this.qrCodeMargin = 3; + } + await Preferences.set({ + key: this.KEY_QR_CODE_MARGIN, + value: JSON.stringify(this.qrCodeMargin), + }); + } + ); + await oldStorage.get(this.KEY_AUTO_MAX_BRIGHTNESS).then( + async value => { + if (value != null) { + this.autoMaxBrightness = value; + } else { + this.autoMaxBrightness = 'off'; + } + await Preferences.set({ + key: this.KEY_AUTO_MAX_BRIGHTNESS, + value: this.autoMaxBrightness, + }); + } + ); + await oldStorage.get(this.KEY_SEARCH_ENGINE).then( + async value => { + if (value != null) { + this.searchEngine = value; + } else { + this.searchEngine = 'google'; + } + await Preferences.set({ + key: this.KEY_SEARCH_ENGINE, + value: this.searchEngine, + }); + } + ); + await oldStorage.get(this.KEY_RESULT_PAGE_BUTTONS).then( + async value => { + if (value != null) { + this.resultPageButtons = value; + } else { + this.resultPageButtons = 'detailed'; + } + await Preferences.set({ + key: this.KEY_RESULT_PAGE_BUTTONS, + value: this.resultPageButtons, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_QR_AFTER_CAMERA_SCAN).then( + async value => { + if (value != null) { + this.showQrAfterCameraScan = value; + } else { + this.showQrAfterCameraScan = 'off'; + } + await Preferences.set({ + key: this.KEY_SHOW_QR_AFTER_CAMERA_SCAN, + value: this.showQrAfterCameraScan, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_QR_AFTER_IMAGE_SCAN).then( + async value => { + if (value != null) { + this.showQrAfterImageScan = value; + } else { + this.showQrAfterImageScan = 'off'; + } + await Preferences.set({ + key: this.KEY_SHOW_QR_AFTER_IMAGE_SCAN, + value: this.showQrAfterImageScan, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_QR_AFTER_CREATE).then( + async value => { + if (value != null) { + this.showQrAfterCreate = value; + } else { + this.showQrAfterCreate = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_QR_AFTER_CREATE, + value: this.showQrAfterCreate, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_QR_AFTER_LOG_VIEW).then( + async value => { + if (value != null) { + this.showQrAfterLogView = value; + } else { + this.showQrAfterLogView = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_QR_AFTER_LOG_VIEW, + value: this.showQrAfterLogView, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_QR_AFTER_BOOKMARK_VIEW).then( + async value => { + if (value != null) { + this.showQrAfterBookmarkView = value; + } else { + this.showQrAfterBookmarkView = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_QR_AFTER_BOOKMARK_VIEW, + value: this.showQrAfterBookmarkView, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_SEARCH_BUTTON).then( + async value => { + if (value != null) { + this.showSearchButton = value; + } else { + this.showSearchButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_SEARCH_BUTTON, + value: this.showSearchButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_COPY_BUTTON).then( + async value => { + if (value != null) { + this.showCopyButton = value; + } else { + this.showCopyButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_COPY_BUTTON, + value: this.showCopyButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_BASE64_BUTTON).then( + async value => { + if (value != null) { + this.showBase64Button = value; + } else { + this.showBase64Button = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_BASE64_BUTTON, + value: this.showBase64Button, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_ENLARGE_BUTTON).then( + async value => { + if (value != null) { + this.showEnlargeButton = value; + } else { + this.showEnlargeButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_ENLARGE_BUTTON, + value: this.showEnlargeButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_BOOKMARK_BUTTON).then( + async value => { + if (value != null) { + this.showBookmarkButton = value; + } else { + this.showBookmarkButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_BOOKMARK_BUTTON, + value: this.showBookmarkButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_OPEN_URL_BUTTON).then( + async value => { + if (value != null) { + this.showOpenUrlButton = value; + } else { + this.showOpenUrlButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_OPEN_URL_BUTTON, + value: this.showOpenUrlButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_BROWSE_BUTTON).then( + async value => { + if (value != null) { + this.showBrowseButton = value; + } else { + this.showBrowseButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_BROWSE_BUTTON, + value: this.showBrowseButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_ADD_CONTACT_BUTTON).then( + async value => { + if (value != null) { + this.showAddContactButton = value; + } else { + this.showAddContactButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_ADD_CONTACT_BUTTON, + value: this.showAddContactButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_CALL_BUTTON).then( + async value => { + if (value != null) { + this.showCallButton = value; + } else { + this.showCallButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_CALL_BUTTON, + value: this.showCallButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_SEND_MESSAGE_BUTTON).then( + async value => { + if (value != null) { + this.showSendMessageButton = value; + } else { + this.showSendMessageButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_SEND_MESSAGE_BUTTON, + value: this.showSendMessageButton, + }); + } + ); + await oldStorage.get(this.KEY_SHOW_SEND_EMAIL_BUTTON).then( + async value => { + if (value != null) { + this.showSendEmailButton = value; + } else { + this.showSendEmailButton = 'on'; + } + await Preferences.set({ + key: this.KEY_SHOW_SEND_EMAIL_BUTTON, + value: this.showSendEmailButton, + }); + } + ); + await oldStorage.get(this.KEY_AUTO_EXIT_MIN).then( + async value => { + if (value != null) { + this.autoExitAppMin = value; + } else { + this.autoExitAppMin = -1; + } + await Preferences.set({ + key: this.KEY_AUTO_EXIT_MIN, + value: JSON.stringify(this.autoExitAppMin), + }); + } + ); + } + await oldStorage.clear(); + } + + private async _loadStorage() { + if (this.platform.is('android')) await Preferences.remove({ key: this.KEY_ANDROID_PREV_NOT_SHOW_UPDATE_NOTES }); + if (this.platform.is('ios')) await Preferences.remove({ key: this.KEY_IOS_PREV_NOT_SHOW_UPDATE_NOTES }); + await Preferences.get({ key: this.KEY_START_PAGE }).then( + async result => { + if (result.value != null) { + this.startPage = result.value as TabPageType; + } else { + this.startPage = '/tabs/scan'; + } + } + ); + await Preferences.get({ key: this.KEY_HISTORY_PAGE_START_SEGMENT }).then( + async result => { + if (result.value != null) { + this.historyPageStartSegment = result.value as HistoryPageSegmentType; + } else { + this.historyPageStartSegment = 'history'; + } + } + ); + await Preferences.get({ key: this.KEY_START_PAGE_HEADER }).then( + async result => { + if (result.value != null) { + this.startPageHeader = result.value as OnOffType; + } else { + this.startPageHeader = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SCAN_RECORDS }).then( + async result => { + if (result.value != null) { + try { + this.scanRecords = JSON.parse(result.value); + this.scanRecords.forEach( + r => { + const tCreatedAt = r.createdAt; + r.createdAt = new Date(tCreatedAt); + } + ); + this.scanRecords.sort((r1, r2) => { + return r2.createdAt.getTime() - r1.createdAt.getTime(); + }); + } catch (err) { + console.error(err); + this.scanRecords = []; + } + } + } + ); + await Preferences.get({ key: this.KEY_BOOKMARKS }).then( + async result => { + if (result.value != null) { + try { + this.bookmarks = JSON.parse(result.value); + this.bookmarks.forEach( + b => { + if (b.id == null) { + b.id = uuidv4(); + } + const tCreatedAt = b.createdAt; + b.createdAt = new Date(tCreatedAt); + } + ); + this.bookmarks.sort((a, b) => { + return ('' + a.tag ?? '').localeCompare(b.tag ?? ''); + }); + } catch (err) { + console.error(err); + this.bookmarks = []; + } + } + } + ) + await Preferences.get({ key: this.KEY_LANGUAGE }).then( + async result => { + if (result.value != null) { + this.selectedLanguage = result.value as 'default' | LanguageType; } else { this.selectedLanguage = 'default'; } this.toggleLanguageChange(); } ); - this.storageGet("color").then( - async value => { - if (value !== null && value !== undefined) { - this.selectedColorTheme = value; + await Preferences.get({ key: this.KEY_COLOR }).then( + async result => { + if (result.value != null) { + this.selectedColorTheme = result.value as 'default' | ColorThemeType; } else { this.selectedColorTheme = 'default'; } await this.toggleColorTheme(); } ); - this.storageGet("scan-record-logging").then( - value => { - if (value !== null && value !== undefined) { - this.scanRecordLogging = value; + await Preferences.get({ key: this.KEY_SHOW_EXIT_APP_ALERT }).then( + async result => { + if (result.value != null) { + this.showExitAppAlert = result.value as OnOffType; + } else { + this.showExitAppAlert = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_DEBUG_MODE }).then( + async result => { + if (result.value != null) { + this.debugMode = result.value as OnOffType; + } else { + this.debugMode = 'off'; + } + } + ); + await Preferences.get({ key: this.KEY_ORIENTATION }).then( + async result => { + if (result.value != null) { + this.orientation = result.value as 'default' | OrientationType; + } else { + this.orientation = 'default'; + } + await this.toggleOrientationChange(); + } + ); + await Preferences.get({ key: this.KEY_SCAN_RECORD_LOGGING }).then( + async result => { + if (result.value != null) { + this.scanRecordLogging = result.value as OnOffType; } else { this.scanRecordLogging = 'on'; } } ); - this.storageGet("vibration").then( - value => { - if (value !== null && value !== undefined) { - this.vibration = value; + await Preferences.get({ key: this.KEY_RECORDS_LIMIT }).then( + async result => { + if (result.value != null) { + this.recordsLimit = JSON.parse(result.value); + } else { + this.recordsLimit = -1; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_NUMBER_OF_RECORDS }).then( + async result => { + if (result.value != null) { + this.showNumberOfRecords = result.value as OnOffType; + } else { + this.showNumberOfRecords = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_VIBRATION }).then( + async result => { + if (result.value != null) { + this.vibration = result.value as VibrationType; } else { this.vibration = 'on'; } } ); - this.storageGet("not-show-history-tutorial").then( - value => { - if (value !== null && value !== undefined) { - this.notShowHistoryTutorial = (value === 'yes' ? true : false); + await Preferences.get({ key: this.KEY_ERROR_CORRECTION_LEVEL }).then( + async result => { + if (result.value != null) { + this.errorCorrectionLevel = result.value as ErrorCorrectionLevelType; } else { - this.notShowHistoryTutorial = false; + this.errorCorrectionLevel = 'M'; } } ); - this.storageGet("search-engine").then( - value => { - if (value !== null && value !== undefined) { - this.searchEngine = value; + await Preferences.get({ key: this.KEY_QR_CODE_LIGHT_R }).then( + async result => { + if (result.value != null) { + this.qrCodeLightR = JSON.parse(result.value); + } else { + this.qrCodeLightR = 255; + } + } + ); + await Preferences.get({ key: this.KEY_QR_CODE_LIGHT_G }).then( + async result => { + if (result.value != null) { + this.qrCodeLightG = JSON.parse(result.value); + } else { + this.qrCodeLightG = 255; + } + } + ); + await Preferences.get({ key: this.KEY_QR_CODE_LIGHT_B }).then( + async result => { + if (result.value != null) { + this.qrCodeLightB = JSON.parse(result.value); + } else { + this.qrCodeLightB = 255; + } + } + ); + await Preferences.get({ key: this.KEY_QR_CODE_DARK_R }).then( + async result => { + if (result.value != null) { + this.qrCodeDarkR = JSON.parse(result.value); + } else { + this.qrCodeDarkR = 34; + } + } + ); + await Preferences.get({ key: this.KEY_QR_CODE_DARK_G }).then( + async result => { + if (result.value != null) { + this.qrCodeDarkG = JSON.parse(result.value); + } else { + this.qrCodeDarkG = 36; + } + } + ); + await Preferences.get({ key: this.KEY_QR_CODE_DARK_B }).then( + async result => { + if (result.value != null) { + this.qrCodeDarkB = JSON.parse(result.value); + } else { + this.qrCodeDarkB = 40; + } + } + ); + await Preferences.get({ key: this.KEY_QR_CODE_MARGIN }).then( + async result => { + if (result.value != null) { + this.qrCodeMargin = JSON.parse(result.value); + } else { + this.qrCodeMargin = 3; + } + } + ); + await Preferences.get({ key: this.KEY_AUTO_MAX_BRIGHTNESS }).then( + async result => { + if (result.value != null) { + this.autoMaxBrightness = result.value as OnOffType; + } else { + this.autoMaxBrightness = 'off'; + } + } + ); + await Preferences.get({ key: this.KEY_AUTO_OPEN_URL }).then( + async result => { + if (result.value != null) { + this.autoOpenUrl = result.value as OnOffType; + } else { + this.autoOpenUrl = 'off'; + } + } + ); + await Preferences.get({ key: this.KEY_SEARCH_ENGINE }).then( + async result => { + if (result.value != null) { + this.searchEngine = result.value as SearchEngineType; } else { this.searchEngine = 'google'; } } ); - this.storageGet(environment.storageScanRecordKey).then( - value => { - if (value !== null && value !== undefined) { - try { - this._scanRecords = JSON.parse(value); - this._scanRecords.forEach( - r => { - const tCreatedAt = r.createdAt; - r.createdAt = new Date(tCreatedAt); - } - ); - this._scanRecords.sort((r1, r2) => { - return r2.createdAt.getTime() - r1.createdAt.getTime(); - }); - } catch (err) { - console.error(err); - this._scanRecords = []; - } - } - } - ); - this.storageGet(environment.storageBookmarkKey).then( - value => { - if (value !== null && value !== undefined) { - try { - this._bookmarks = JSON.parse(value); - this._bookmarks.forEach( - t => { - const tCreatedAt = t.createdAt; - t.createdAt = new Date(tCreatedAt); - } - ); - this._bookmarks.sort((t1, t2) => { - return t2.createdAt.getTime() - t1.createdAt.getTime(); - }); - } catch (err) { - console.error(err); - this._bookmarks = []; - } - } - } - ) - this.storageGet("debug-mode-on").then( - value => { - if (value != null) { - this.debugModeOn = value; + await Preferences.get({ key: this.KEY_RESULT_PAGE_BUTTONS }).then( + async result => { + if (result.value != null) { + this.resultPageButtons = result.value as ResultPageButtonsType; } else { - this.debugModeOn = 'off'; + this.resultPageButtons = 'detailed'; } } ); - } - - public async storageSet(key: string, value: any) { - await this._storage?.set(key, value); - } - - public async storageGet(key: string): Promise { - const value = await this._storage?.get(key).then( - value => { - return value; - }, - err => { - console.error("error when get from storage", err); - return null; + await Preferences.get({ key: this.KEY_SHOW_QR_AFTER_CAMERA_SCAN }).then( + async result => { + if (result.value != null) { + this.showQrAfterCameraScan = result.value as OnOffType; + } else { + this.showQrAfterCameraScan = 'off'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_QR_AFTER_IMAGE_SCAN }).then( + async result => { + if (result.value != null) { + this.showQrAfterImageScan = result.value as OnOffType; + } else { + this.showQrAfterImageScan = 'off'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_QR_AFTER_CREATE }).then( + async result => { + if (result.value != null) { + this.showQrAfterCreate = result.value as OnOffType; + } else { + this.showQrAfterCreate = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_QR_AFTER_LOG_VIEW }).then( + async result => { + if (result.value != null) { + this.showQrAfterLogView = result.value as OnOffType; + } else { + this.showQrAfterLogView = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_QR_AFTER_BOOKMARK_VIEW }).then( + async result => { + if (result.value != null) { + this.showQrAfterBookmarkView = result.value as OnOffType; + } else { + this.showQrAfterBookmarkView = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_SEARCH_BUTTON }).then( + async result => { + if (result.value != null) { + this.showSearchButton = result.value as OnOffType; + } else { + this.showSearchButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_COPY_BUTTON }).then( + async result => { + if (result.value != null) { + this.showCopyButton = result.value as OnOffType; + } else { + this.showCopyButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_BASE64_BUTTON }).then( + async result => { + if (result.value != null) { + this.showBase64Button = result.value as OnOffType; + } else { + this.showBase64Button = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_ENLARGE_BUTTON }).then( + async result => { + if (result.value != null) { + this.showEnlargeButton = result.value as OnOffType; + } else { + this.showEnlargeButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_BOOKMARK_BUTTON }).then( + async result => { + if (result.value != null) { + this.showBookmarkButton = result.value as OnOffType; + } else { + this.showBookmarkButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_OPEN_URL_BUTTON }).then( + async result => { + if (result.value != null) { + this.showOpenUrlButton = result.value as OnOffType; + } else { + this.showOpenUrlButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_BROWSE_BUTTON }).then( + async result => { + if (result.value != null) { + this.showBrowseButton = result.value as OnOffType; + } else { + this.showBrowseButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_ADD_CONTACT_BUTTON }).then( + async result => { + if (result.value != null) { + this.showAddContactButton = result.value as OnOffType; + } else { + this.showAddContactButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_CALL_BUTTON }).then( + async result => { + if (result.value != null) { + this.showCallButton = result.value as OnOffType; + } else { + this.showCallButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_SEND_MESSAGE_BUTTON }).then( + async result => { + if (result.value != null) { + this.showSendMessageButton = result.value as OnOffType; + } else { + this.showSendMessageButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_SEND_EMAIL_BUTTON }).then( + async result => { + if (result.value != null) { + this.showSendEmailButton = result.value as OnOffType; + } else { + this.showSendEmailButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_SHOW_OPEN_FOOD_FACTS_BUTTON }).then( + async result => { + if (result.value != null) { + this.showOpenFoodFactsButton = result.value as OnOffType; + } else { + this.showOpenFoodFactsButton = 'on'; + } + } + ); + await Preferences.get({ key: this.KEY_AUTO_EXIT_MIN }).then( + async result => { + if (result.value != null) { + this.autoExitAppMin = JSON.parse(result.value); + } else { + this.autoExitAppMin = -1; + } } ); - return value; } public async resetAll() { - await this._storage.clear(); + await Preferences.clear(); + this.startPage = '/tabs/scan'; + this.historyPageStartSegment = 'history'; + this.startPageHeader = 'on'; this.selectedLanguage = 'default'; this.toggleLanguageChange(); this.selectedColorTheme = 'default'; await this.toggleColorTheme(); this.scanRecordLogging = 'on'; + this.recordsLimit = -1; + this.showNumberOfRecords = 'on'; + this.autoMaxBrightness = 'off'; + this.autoOpenUrl = 'off'; + this.errorCorrectionLevel = 'M'; + this.qrCodeLightR = 255; + this.qrCodeLightG = 255; + this.qrCodeLightB = 255; + this.qrCodeDarkR = 34; + this.qrCodeDarkG = 36; + this.qrCodeDarkB = 40; + this.qrCodeMargin = 3; this.vibration = 'on'; - this.notShowHistoryTutorial = false; + this.orientation = 'default'; + await this.toggleOrientationChange(); this.notShowUpdateNotes = false; this.searchEngine = 'google'; - this._scanRecords = []; - this._bookmarks = []; - this.debugModeOn = 'off'; + this.resultPageButtons = 'detailed'; + this.showQrAfterCameraScan = 'off'; + this.showQrAfterImageScan = 'off'; + this.showQrAfterCreate = 'on'; + this.showQrAfterLogView = 'on'; + this.showQrAfterBookmarkView = 'on'; + this.showSearchButton = 'on'; + this.showCopyButton = 'on'; + this.showBase64Button = 'on'; + this.showEnlargeButton = 'on'; + this.showBookmarkButton = 'on'; + this.showOpenUrlButton = 'on'; + this.showBrowseButton = 'on'; + this.showAddContactButton = 'on'; + this.showCallButton = 'on'; + this.showSendMessageButton = 'on'; + this.showSendEmailButton = 'on'; + this.showOpenFoodFactsButton = 'on'; + this.scanRecords = []; + this.bookmarks = []; + this.showExitAppAlert = 'on'; + this.debugMode = 'off'; + this.autoExitAppMin = -1; } - get result(): string { - return this._scannedData; + public async resetData() { + await this.deleteAllScanRecords(); + await this.deleteAllBookmarks(); } - set result(value: string) { - this._scannedData = value; + public async resetSetting() { + this.startPage = '/tabs/scan'; + await Preferences.set({ key: this.KEY_START_PAGE, value: this.startPage }); + + this.historyPageStartSegment = 'history'; + await Preferences.set({ key: this.KEY_HISTORY_PAGE_START_SEGMENT, value: this.historyPageStartSegment }); + + this.startPageHeader = 'on'; + await Preferences.set({ key: this.KEY_START_PAGE_HEADER, value: this.startPageHeader }); + + this.selectedLanguage = 'default'; + this.toggleLanguageChange(); + await Preferences.set({ key: this.KEY_LANGUAGE, value: this.selectedLanguage }); + + this.selectedColorTheme = 'default'; + await this.toggleColorTheme(); + await Preferences.set({ key: this.KEY_COLOR, value: this.selectedColorTheme }); + + this.scanRecordLogging = 'on'; + await Preferences.set({ key: this.KEY_SCAN_RECORD_LOGGING, value: this.scanRecordLogging }); + + this.recordsLimit = -1; + await Preferences.set({ key: this.KEY_RECORDS_LIMIT, value: JSON.stringify(this.recordsLimit) }); + + this.showNumberOfRecords = 'on'; + await Preferences.set({ key: this.KEY_SHOW_NUMBER_OF_RECORDS, value: this.showNumberOfRecords }); + + this.autoMaxBrightness = 'off'; + await Preferences.set({ key: this.KEY_AUTO_MAX_BRIGHTNESS, value: this.autoMaxBrightness }); + + this.autoOpenUrl = 'off'; + await Preferences.set({ key: this.KEY_AUTO_OPEN_URL, value: this.autoOpenUrl }); + + this.errorCorrectionLevel = 'M'; + await Preferences.set({ key: this.KEY_ERROR_CORRECTION_LEVEL, value: this.errorCorrectionLevel }); + + this.qrCodeLightR = 255; + await Preferences.set({ key: this.KEY_QR_CODE_LIGHT_R, value: JSON.stringify(this.qrCodeLightR) }); + + this.qrCodeLightG = 255; + await Preferences.set({ key: this.KEY_QR_CODE_LIGHT_G, value: JSON.stringify(this.qrCodeLightG) }); + + this.qrCodeLightB = 255; + await Preferences.set({ key: this.KEY_QR_CODE_LIGHT_B, value: JSON.stringify(this.qrCodeLightB) }); + + this.qrCodeDarkR = 34; + await Preferences.set({ key: this.KEY_QR_CODE_DARK_R, value: JSON.stringify(this.qrCodeDarkR) }); + + this.qrCodeDarkG = 36; + await Preferences.set({ key: this.KEY_QR_CODE_DARK_G, value: JSON.stringify(this.qrCodeDarkG) }); + + this.qrCodeDarkB = 40; + await Preferences.set({ key: this.KEY_QR_CODE_DARK_B, value: JSON.stringify(this.qrCodeDarkB) }); + + this.qrCodeMargin = 3; + await Preferences.set({ key: this.KEY_QR_CODE_MARGIN, value: JSON.stringify(this.qrCodeMargin) }); + this.vibration = 'on'; + await Preferences.set({ key: this.KEY_VIBRATION, value: this.vibration }); + + this.orientation = 'default'; + await this.toggleOrientationChange(); + await Preferences.set({ key: this.KEY_ORIENTATION, value: this.orientation }); + + this.notShowUpdateNotes = false; + if (this.platform.is('ios')) { + await Preferences.set({ key: this.KEY_IOS_NOT_SHOW_UPDATE_NOTES, value: 'no' }); + } else if (this.platform.is('android')) { + await Preferences.set({ key: this.KEY_ANDROID_NOT_SHOW_UPDATE_NOTES, value: 'no' }); + } + + this.searchEngine = 'google'; + await Preferences.set({ key: this.KEY_SEARCH_ENGINE, value: this.searchEngine }); + + this.resultPageButtons = 'detailed'; + await Preferences.set({ key: this.KEY_RESULT_PAGE_BUTTONS, value: this.resultPageButtons }); + + this.showQrAfterCameraScan = 'off'; + await Preferences.set({ key: this.KEY_SHOW_QR_AFTER_CAMERA_SCAN, value: this.showQrAfterCameraScan }); + + this.showQrAfterImageScan = 'off'; + await Preferences.set({ key: this.KEY_SHOW_QR_AFTER_IMAGE_SCAN, value: this.showQrAfterImageScan }); + + this.showQrAfterCreate = 'on'; + await Preferences.set({ key: this.KEY_SHOW_QR_AFTER_CREATE, value: this.showQrAfterCreate }); + + this.showQrAfterLogView = 'on'; + await Preferences.set({ key: this.KEY_SHOW_QR_AFTER_LOG_VIEW, value: this.showQrAfterLogView }); + + this.showQrAfterBookmarkView = 'on'; + await Preferences.set({ key: this.KEY_SHOW_QR_AFTER_BOOKMARK_VIEW, value: this.showQrAfterBookmarkView }); + + this.showSearchButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_SEARCH_BUTTON, value: this.showSearchButton }); + + this.showCopyButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_COPY_BUTTON, value: this.showCopyButton }); + + this.showBase64Button = 'on'; + await Preferences.set({ key: this.KEY_SHOW_BASE64_BUTTON, value: this.showBase64Button }); + + this.showEnlargeButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_ENLARGE_BUTTON, value: this.showEnlargeButton }); + + this.showBookmarkButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_BOOKMARK_BUTTON, value: this.showBookmarkButton }); + + this.showOpenUrlButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_OPEN_URL_BUTTON, value: this.showOpenUrlButton }); + + this.showBrowseButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_BROWSE_BUTTON, value: this.showBrowseButton }); + + this.showAddContactButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_ADD_CONTACT_BUTTON, value: this.showAddContactButton }); + + this.showCallButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_CALL_BUTTON, value: this.showCallButton }); + + this.showSendMessageButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_SEND_MESSAGE_BUTTON, value: this.showSendMessageButton }); + + this.showSendEmailButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_SEND_EMAIL_BUTTON, value: this.showSendEmailButton }); + + this.showOpenFoodFactsButton = 'on'; + await Preferences.set({ key: this.KEY_SHOW_OPEN_FOOD_FACTS_BUTTON, value: this.showOpenFoodFactsButton }); + + this.showExitAppAlert = 'on'; + await Preferences.set({ key: this.KEY_SHOW_EXIT_APP_ALERT, value: this.showExitAppAlert }); + + this.debugMode = 'off'; + await Preferences.set({ key: this.KEY_DEBUG_MODE, value: this.debugMode }); + + this.autoExitAppMin = -1; + await Preferences.set({ key: this.KEY_AUTO_EXIT_MIN, value: JSON.stringify(this.autoExitAppMin) }); } - get resultFormat(): string { - return this._scannedDataFormat; - } + async resetQrCodeSettings() { + this.errorCorrectionLevel = 'M'; + await Preferences.set({ key: this.KEY_ERROR_CORRECTION_LEVEL, value: this.errorCorrectionLevel }); - set resultFormat(value: string) { - this._scannedDataFormat = value; - } + this.qrCodeLightR = 255; + await Preferences.set({ key: this.KEY_QR_CODE_LIGHT_R, value: JSON.stringify(this.qrCodeLightR) }); - get scanRecords(): ScanRecord[] { - return this._scanRecords; + this.qrCodeLightG = 255; + await Preferences.set({ key: this.KEY_QR_CODE_LIGHT_G, value: JSON.stringify(this.qrCodeLightG) }); + + this.qrCodeLightB = 255; + await Preferences.set({ key: this.KEY_QR_CODE_LIGHT_B, value: JSON.stringify(this.qrCodeLightB) }); + + this.qrCodeDarkR = 34; + await Preferences.set({ key: this.KEY_QR_CODE_DARK_R, value: JSON.stringify(this.qrCodeDarkR) }); + + this.qrCodeDarkG = 36; + await Preferences.set({ key: this.KEY_QR_CODE_DARK_G, value: JSON.stringify(this.qrCodeDarkG) }); + + this.qrCodeDarkB = 40; + await Preferences.set({ key: this.KEY_QR_CODE_DARK_B, value: JSON.stringify(this.qrCodeDarkB) }); + + this.qrCodeMargin = 3; + await Preferences.set({ key: this.KEY_QR_CODE_MARGIN, value: JSON.stringify(this.qrCodeMargin) }); } async saveScanRecord(value: string): Promise { @@ -238,124 +1422,241 @@ export class EnvService { record.id = String(date.getTime()); record.text = value; record.createdAt = date; - this._scanRecords.unshift(record); - await this.storageSet(environment.storageScanRecordKey, JSON.stringify(this._scanRecords)); + if (this.recordSource != null) { + record.source = this.recordSource; + if (this.recordSource == 'scan') { + record.barcodeType = this.resultContentFormat; + } + } else { + record.source = "view"; + } + if (this.scanRecords == null) { + this.scanRecords = []; + } + this.scanRecords.unshift(record); + if (this.recordsLimit != -1) { + if (this.scanRecords.length > this.recordsLimit) { + this.scanRecords = this.scanRecords.slice(0, this.recordsLimit); + } + } + try { + const stringified = JSON.stringify(this.scanRecords); + await Preferences.set({ key: this.KEY_SCAN_RECORDS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify scanRecords: " + JSON.stringify(e), "long", "top"); + } + } } async saveRestoredScanRecords(records: ScanRecord[]): Promise { records.forEach( r => { - this._scanRecords.unshift(r); + this.scanRecords.unshift(r); } ); - this._scanRecords.forEach( + this.scanRecords.forEach( t => { const tCreatedAt = t.createdAt; t.createdAt = new Date(tCreatedAt); } ); - this._scanRecords.sort((r1, r2) => { + this.scanRecords.sort((r1, r2) => { return r2.createdAt.getTime() - r1.createdAt.getTime(); }); - await this.storageSet(environment.storageScanRecordKey, JSON.stringify(this._scanRecords)); + try { + const stringified = JSON.stringify(this.scanRecords); + await Preferences.set({ key: this.KEY_SCAN_RECORDS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify scanRecords: " + JSON.stringify(e), "long", "top"); + } + } } async saveRestoredBookmarks(bookmarks: Bookmark[]): Promise { bookmarks.forEach( b => { - this._bookmarks.unshift(b); + this.bookmarks.unshift(b); } ); - this._bookmarks.forEach( - t => { - const tCreatedAt = t.createdAt; - t.createdAt = new Date(tCreatedAt); + this.bookmarks.forEach( + b => { + if (b.id == null) { + b.id = uuidv4(); + } + const tCreatedAt = b.createdAt; + b.createdAt = new Date(tCreatedAt); } ); - this._bookmarks.sort((r1, r2) => { - return r2.createdAt.getTime() - r1.createdAt.getTime(); + this.bookmarks.sort((a, b) => { + return ('' + a.tag ?? '').localeCompare(b.tag ?? ''); }); - await this.storageSet(environment.storageBookmarkKey, JSON.stringify(this._bookmarks)); + try { + const stringified = JSON.stringify(this.bookmarks); + await Preferences.set({ key: this.KEY_BOOKMARKS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify bookmarks: " + JSON.stringify(e), "long", "top"); + } + } } async undoScanRecordDeletion(record: ScanRecord): Promise { - this._scanRecords.push(record); - this._scanRecords.sort((r1, r2) => { + this.scanRecords.push(record); + this.scanRecords.sort((r1, r2) => { return r2.createdAt.getTime() - r1.createdAt.getTime(); }); - await this.storageSet(environment.storageScanRecordKey, JSON.stringify(this._scanRecords)); + try { + const stringified = JSON.stringify(this.scanRecords); + await Preferences.set({ key: this.KEY_SCAN_RECORDS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify scanRecords: " + JSON.stringify(e), "long", "top"); + } + } } async deleteScanRecord(recordId: string): Promise { - const index = this._scanRecords.findIndex(r => r.id === recordId); + const index = this.scanRecords.findIndex(r => r.id === recordId); if (index !== -1) { - this._scanRecords.splice(index, 1); - await this.storageSet(environment.storageScanRecordKey, JSON.stringify(this._scanRecords)); + this.scanRecords.splice(index, 1); + try { + const stringified = JSON.stringify(this.scanRecords); + await Preferences.set({ key: this.KEY_SCAN_RECORDS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify scanRecords: " + JSON.stringify(e), "long", "top"); + } + } } } async deleteAllScanRecords(): Promise { - this._scanRecords = []; - await this.storageSet(environment.storageScanRecordKey, JSON.stringify(this._scanRecords)); + this.scanRecords = []; + try { + const stringified = JSON.stringify(this.scanRecords); + await Preferences.set({ key: this.KEY_SCAN_RECORDS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify scanRecords: " + JSON.stringify(e), "long", "top"); + } + } } - get bookmarks(): Bookmark[] { - return this._bookmarks; - } - - async saveBookmark(value: string): Promise { - const index = this._bookmarks.findIndex(x => x.text === value); + async saveBookmark(value: string, tag: string): Promise { + const index = this.bookmarks.findIndex(x => x.text === value); if (index === -1) { const bookmark = new Bookmark(); const date = new Date(); + bookmark.id = uuidv4(); bookmark.text = value; bookmark.createdAt = date; - this._bookmarks.unshift(bookmark); - await this.storageSet(environment.storageBookmarkKey, JSON.stringify(this._bookmarks)); - return true; + bookmark.tag = tag; + this.bookmarks.unshift(bookmark); + this.bookmarks.sort((a, b) => { + return ('' + a.tag ?? '').localeCompare(b.tag ?? ''); + }); + try { + const stringified = JSON.stringify(this.bookmarks); + await Preferences.set({ key: this.KEY_BOOKMARKS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify bookmarks: " + JSON.stringify(e), "long", "top"); + } + } + return bookmark; } else { - return false; + return null; } } async undoBookmarkDeletion(bookmark: Bookmark): Promise { - this._bookmarks.push(bookmark); - this._bookmarks.sort((t1, t2) => { - return t2.createdAt.getTime() - t1.createdAt.getTime(); + this.bookmarks.push(bookmark); + this.bookmarks.sort((a, b) => { + return ('' + a.tag ?? '').localeCompare(b.tag ?? ''); }); - await this.storageSet(environment.storageBookmarkKey, JSON.stringify(this._bookmarks)); + try { + const stringified = JSON.stringify(this.bookmarks); + await Preferences.set({ key: this.KEY_BOOKMARKS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify bookmarks: " + JSON.stringify(e), "long", "top"); + } + } } async deleteBookmark(text: string): Promise { - const index = this._bookmarks.findIndex(t => t.text === text); + const index = this.bookmarks.findIndex(t => t.text === text); if (index !== -1) { - this._bookmarks.splice(index, 1); - await this.storageSet(environment.storageBookmarkKey, JSON.stringify(this._bookmarks)); + this.bookmarks.splice(index, 1); + try { + const stringified = JSON.stringify(this.bookmarks); + await Preferences.set({ key: this.KEY_BOOKMARKS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify bookmarks: " + JSON.stringify(e), "long", "top"); + } + } } } async deleteAllBookmarks(): Promise { - this._bookmarks = []; - await this.storageSet(environment.storageBookmarkKey, JSON.stringify(this._bookmarks)); + this.bookmarks = []; + try { + const stringified = JSON.stringify(this.bookmarks); + await Preferences.set({ key: this.KEY_BOOKMARKS, value: stringified }); + } catch (e) { + if (this.isDebugging) { + this.presentToast("Err when stringify bookmarks: " + JSON.stringify(e), "long", "top"); + } + } } toggleLanguageChange() { - if (this.selectedLanguage === 'default') { + if (this.selectedLanguage == 'default') { let language = 'en'; - const browserLang = this.translate.getBrowserCultureLang(); - console.log("browserLang", browserLang); - if (browserLang.includes("zh", 0)) { - if (browserLang === 'zh-CN' || browserLang === 'zh-SG') language = 'zh-CN'; - else language = "zh-HK"; - } else if (browserLang.includes("yue", 0)) { - language = "zh-HK"; - } else if (this.languages.includes(browserLang)) { - language = browserLang as 'en' | 'zh-HK' | 'zh-CN'; - } else { + const browserCultureLang = this.translate.getBrowserCultureLang(); + if (browserCultureLang == null) { language = 'en'; + } else { + const lang = browserCultureLang.slice(0, 2)?.toLowerCase(); + switch (lang) { + case "de": + language = "de"; + break; + case "en": + language = "en" + break; + case "fr": + language = "fr" + break; + case "it": + language = "it" + break; + case "pt": + language = "pt-BR"; + break; + case "ru": + language = "ru" + break; + case "zh": + if (browserCultureLang == 'zh-CN' || browserCultureLang == 'zh-SG') { + language = 'zh-CN'; + } else { + language = "zh-HK"; + } + break; + default: + if (browserCultureLang.slice(0, 3) == "yue") { + language = "zh-HK"; + } else { + language = 'en'; + } + } } this.translate.use(language); - this.language = language as 'en' | 'zh-HK' | 'zh-CN'; + this.language = language as LanguageType; } else { this.translate.use(this.selectedLanguage); this.language = this.selectedLanguage; @@ -363,7 +1664,6 @@ export class EnvService { } async toggleColorTheme(): Promise { - console.log("toggle color!") if (this.selectedColorTheme === 'default') { const version = Number(this._deviceInfo?.osVersion.split(".")[0] ?? 0); if (this.platform.is("android") && version <= 9) { // Android 9 or below @@ -429,56 +1729,59 @@ export class EnvService { } } - /** - Developer, + async toggleOrientationChange(): Promise { + switch (this.orientation) { + case 'default': + this.screenOrientation.unlock(); + return; + case 'portrait': + await this.screenOrientation.lock(this.screenOrientation.ORIENTATIONS.PORTRAIT) + .catch(err => { + if (this.isDebugging) { + this.presentToast("Error when ScreenOrientation.lock(p): " + JSON.stringify(err), "long", "top"); + } + }); + return; + case 'landscape': + await this.screenOrientation.lock(this.screenOrientation.ORIENTATIONS.LANDSCAPE) + .catch(err => { + if (this.isDebugging) { + this.presentToast("Error when ScreenOrientation.lock(l): " + JSON.stringify(err), "long", "top"); + } + }); + return; + default: + this.screenOrientation.unlock(); + } + } - I would like to report an issue regarding Simple QR. - - Date & Time - {datetimestr2} - - App Version - {appVersion} - - Model - {model} - - Platform - {os} {osVersion} - - Description - (describe the issue below) - - */ - async getBugReportMailContent(): Promise { + getBugReportMailContent(): string { const toEmail = "tomfong.dev@gmail.com"; - const now = moment(); - const datetimestr1 = now.format("YYYYMMDDHHmmss"); - const datetimestr2 = now.format("YYYY-MM-DD HH:mm:ss ZZ"); - const appVersion = this.appVersionNumber + '.' + this.buildEnv; + const now = new Date(); + const datetimestr1 = format(now, "yyyyMMddHHmmss"); + const datetimestr2 = format(now, "yyyy-MM-dd HH:mm:ss zzzz"); const model = `${this._deviceInfo?.manufacturer} ${this._deviceInfo?.model}`; const os = this.platform.is("android") ? "Android" : (this.platform.is("ios") ? "iOS" : "Other"); const osVersion = this._deviceInfo?.osVersion; - let mailContent: string; - switch (this.language) { - case 'en': - mailContent = ` - mailto:${toEmail}?subject=Simple%20QR%20-%20Report%20Issue%20(%23${datetimestr1})&body=Developer%2C%0A%0AI%20would%20like%20to%20report%20an%20issue%20regarding%20Simple%20QR.%0A%0ADate%20%26%20Time%0A${datetimestr2}%0A%0AApp%20Version%0A${appVersion}%0A%0AModel%0A${model}%0A%0APlatform%0A${os}%20${osVersion}%0A%0ADescription%0D%0A(describe%20the%20issue%20below)%0D%0A%0D%0A - ` // must be in a line - break; - case 'zh-HK': - mailContent = ` - mailto:${toEmail}?subject=%E7%B0%A1%E6%98%93QR%20-%20%E5%9B%9E%E5%A0%B1%E5%95%8F%E9%A1%8C%20(%23${datetimestr1})&body=%E9%96%8B%E7%99%BC%E4%BA%BA%E5%93%A1%EF%BC%9A%0A%0A%E6%9C%AC%E4%BA%BA%E6%AC%B2%E5%9B%9E%E5%A0%B1%E6%9C%89%E9%97%9C%E3%80%8C%E7%B0%A1%E6%98%93QR%E3%80%8D%E7%9A%84%E5%95%8F%E9%A1%8C%E3%80%82%0A%0A%E6%97%A5%E6%9C%9F%E5%8F%8A%E6%99%82%E9%96%93%0A${datetimestr2}%0A%0A%E7%A8%8B%E5%BC%8F%E7%89%88%E6%9C%AC%0A${appVersion}%0A%0A%E5%9E%8B%E8%99%9F%0A${model}%0A%0A%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%B5%B1%0A${os}%20${osVersion}%0A%0A%E5%95%8F%E9%A1%8C%E6%8F%8F%E8%BF%B0%0A(%E8%AB%8B%E6%96%BC%E4%B8%8B%E6%96%B9%E6%8F%8F%E8%BF%B0%E5%95%8F%E9%A1%8C)%0D%0A%0D%0A - ` // must be in a line - break; - default: - mailContent = ` - mailto:${toEmail}?subject=Simple%20QR%20-%20Report%20Issue%20(%23${datetimestr1})&body=Developer%2C%0A%0AI%20would%20like%20to%20report%20an%20issue%20regarding%20Simple%20QR.%0A%0ADate%20%26%20Time%0A${datetimestr2}%0A%0AApp%20Version%0A${appVersion}%0A%0AModel%0A${model}%0A%0APlatform%0A${os}%20${osVersion}%0A%0ADescription%0D%0A(describe%20the%20issue%20below)%0D%0A%0D%0A - ` // must be in a line - } + const mailContent = + ` + mailto:${toEmail}?subject=Simple%20QR%20-%20Report%20Issue%20(%23${datetimestr1})&body=Date%20%26%20Time%0A${datetimestr2}%0A%0AApp%20Version%0A${this.appVersionNumber}%0A%0AModel%0A${model}%0A%0APlatform%0A${os}%20${osVersion}%0A%0ADescription%0D%0A(describe%20the%20issue%20below)%0D%0A%0D%0A + `; return mailContent; } + async presentToast(msg: string, duration: "short" | "long", pos: "top" | "center" | "bottom") { + await Toast.show({ + text: msg, + duration: duration, + position: pos + }); + } + + get isDebugging(): boolean { + return this.debugMode === 'on'; + } + get buildEnv(): string { return environment.production ? '' : '.Dev'; } diff --git a/src/app/utils/animations.ts b/src/app/utils/animations.ts new file mode 100644 index 0000000..98ba423 --- /dev/null +++ b/src/app/utils/animations.ts @@ -0,0 +1,29 @@ +import { trigger, transition, style, animate, state } from "@angular/animations"; + +export const fadeIn = trigger('inAnimation', [ + transition(':enter', [ + style({ opacity: 0 }), + animate('1s ease-out', style({ opacity: 1 })) + ]) +]) + +export const fastFadeIn = trigger('fastInAnimation', [ + transition(':enter', [ + style({ opacity: 0 }), + animate('.5s ease', style({ opacity: 1 })) + ]) +]) + +export const fadeOut = trigger('outAnimation', [ + transition(':leave', [ + style({ opacity: 1 }), + animate('.3s ease', style({ opacity: 0 })) + ]) +]) + +export const flyOut = trigger('flyOut', [ + transition(':leave', [ + style({ opacity: 1 }), + animate('.5s ease-in', style({ transform: 'translateX(-100%)', opacity: 0 })) + ]) +]) \ No newline at end of file diff --git a/src/app/utils/helpers.ts b/src/app/utils/helpers.ts new file mode 100644 index 0000000..f3d5a54 --- /dev/null +++ b/src/app/utils/helpers.ts @@ -0,0 +1,15 @@ +import { HttpClient } from "@angular/common/http"; +import { TranslateHttpLoader } from "@ngx-translate/http-loader"; + +function componentToHex(c: number) { + var hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex; +} + +export function rgbToHex(r: number, g: number, b: number) { + return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); +} + +export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json new file mode 100644 index 0000000..21f954e --- /dev/null +++ b/src/assets/i18n/de.json @@ -0,0 +1,322 @@ +{ + "100_RECORDS": "100 Aufzeichnungen", + "30_RECORDS": "30 Aufzeichnungen", + "49_DIGIT": "49-digit", + "50_RECORDS": "50 Aufzeichnungen", + "ABOUT": "Über", + "ABOUT_SIMPLE_QR": "Über Simple QR", + "ADD": "Hinzufügen", + "ADD_BCC": "BCC hinzufügen", + "ADD_CC": "CC hinzufügen", + "ADD_CONTACT": "Kontakt hinzufügen", + "ADD_RECIPIENT": "Empfänger hinzufügen", + "APP": "App", + "APPEARANCE_AND_EFFECTS": "Erscheinungsbild & Effekte", + "APP_INITIAL_PAGE": "App Startseite", + "APP_VERSION": "App Version", + "AT": "um", + "AT_LEAST_1_MINUTE_LATER": "Mindestens 1 Minute später", + "AT_LEAST_3_MINUTES_LATER": "Mindestens 3 Minuten später", + "AT_LEAST_5_MINUTES_LATER": "Mindestens 5 Minuten später", + "AUTO_KILL_BACKGROUND": "Auto Kill Hintergrund", + "AUTO_LOGGING": "Automatische Protokollierung", + "AUTO_MAX_BRIGHTNESS": "Auto Maximale Helligkeit", + "AUTO_OPEN_URL": "URL automatisch öffnen", + "AUTO_QR_CODE_POPUP": "Automatisches QR Code Pop-up", + "BACKGROUND_COLOR": "Hintergrundfarbe", + "BACKING_UP": "Sichern", + "BACKUP": "Sicherung", + "BASE64": "Base64", + "BASE64_DECODED": "Base64 Entschlüsselt", + "BASE64_ENCODED": "Base64 Verschlüsselt", + "BCC": "BCC", + "BLACK": "Schwarz", + "BOOKMARK": "Lesezeichen", + "BOOKMARKED": "Lesezeichen", + "BOOKMARKED_TEXTS": "Texte mit Lesezeichen", + "BOOKMARKS": "Lesezeichen", + "BRAVE_SEARCH": "Brave Search", + "BROWSE": "Durchsuchen", + "BROWSER": "Browser", + "BROWSE_WEBSITE": "Website durchsuchen", + "CALL": "Aufruf", + "CANCEL": "Abbrechen", + "CC": "CC", + "CITY": "Stadt", + "CLEAR": "Löschen", + "CLOSE": "Schließen", + "COLOR": "Farbe", + "COLOR_THEME": "Farbthema", + "COMING_SOON": "Bald verfügbar", + "CONTACT_METHOD": "Kontakt Methode", + "CONTACT_NAME": "Kontakt Name", + "CONTENT": "Inhalt", + "CONTENT_TYPE": "Inhaltstyp", + "COPIED": "Kopiert", + "COPY": "Kopieren", + "COPY_SECRET_AND_SAVE_BACKUP": "Geheimnis kopieren & Backup speichern", + "COPY_TEXT": "Text kopieren", + "COUNTRY": "Land", + "CREATE": "Erstellen", + "CREATED": "Erstellt", + "CREATE_QR_CODE": "QR Code Erstellen", + "DARK": "Dunkel", + "DATE_OF_BIRTH": "Geburtsdatum", + "DEBUG_MODE": "Fehlersuchmodus", + "DECODING": "Entschlüsselung", + "DECRYPTING": "Entschlüsseln", + "DETAILED": "Detailliert", + "DONE": "Erledigt", + "DUCK_DUCK_GO": "DuckDuckGo", + "ECOSIA": "Ecosia", + "EDIT": "Bearbeiten", + "EMAIL_ADDRESS": "Email Adresse", + "EMAIL_BODY": "Email Stelle", + "EMAIL_NTT_DOCOMO": "Email (NTT Docomo)", + "EMAIL_RECIPIENT": "Email Empfänger", + "EMAIL_SUBJECT": "Email Betreff", + "EMAIL_TO": "Email an", + "EMAIL_W3C_STANDARD": "Email (W3C Standard)", + "ENCRYPTING": "Verschlüsseln", + "ERROR_CORRECTION_LEVEL": "Fehlerkorrektur-Level", + "EXIT": "Beenden", + "EXIT_APP": "App beenden", + "EXPORT": "Exportieren", + "EXPORTING": "Exportieren", + "EXPORT_TO_CSV": "In CSV exportieren", + "FAX_NUMBER": "Fax Nummer", + "FEMALE": "Weiblich", + "FIRST_NAME": "Vorname", + "FOLLOW_SYSTEM_SETTINGS": "Systemeinstellungen folgen", + "FREE_TEXT": "Freier Text", + "FULL_RESET": "Voller Reset", + "FUNCTIONS": "Funktionen", + "GENDER": "Geschlecht", + "GEOLOCATION": "Geolokalisierung", + "GOOGLE_SEARCH": "Google-Suche", + "HAPTIC_FEEDBACK_ONLY": "Nur haptisches Feedback", + "HIDDEN_NETWORK_?": "Verstecktes Netzwerk?", + "HOME_ADDRESS": "Anschrift", + "HOME_PHONE_NUMBER": "Telefonnummer zu Hause", + "ICON_ONLY": "Nur Icon", + "IMPORT": "Importieren", + "IMPORT_FROM_CSV": "Aus CSV importieren", + "IMPORT_IMAGE": "Bild importieren", + "INITIAL_SEGMENT": "Erstes Segment", + "JOB_TITLE": "Berufsbezeichnung", + "LANGUAGE": "Sprache", + "LAST_NAME": "Nachname", + "LATITUDE": "Breitengrad", + "LEVEL_H": "Level H", + "LEVEL_L": "Level L", + "LEVEL_M": "Level M", + "LEVEL_Q": "Level Q", + "LIGHT": "Hell", + "LOADING_DATA": "Laden von Daten", + "LOCK_LANDSCAPE": "Landscape sperren", + "LOCK_PORTRAIT": "Porträt sperren", + "LOG": "Protokoll", + "LOG_BACKUP_AND_RESTORE": "Protokoll, Sicherung und Wiederherstellung", + "LONGITUDE": "Längengrad", + "MALE": "Männlich", + "MANAGE_RECORDS": "Aufzeichnungen verwalten", + "MARGIN": "Rand", + "MESSAGE": "Nachricht", + "MESSAGE_CONTENT": "Inhalt der Nachricht", + "MICROSOFT_BING": "Microsoft Bing", + "MOBILE_PHONE_NUMBER": "Mobiltelefon Nummer", + "MORE": "Mehr", + "NAME": "Name", + "NO": "Nein", + "NONE": "Keine", + "NOT_AVAILABLE": "Nicht verfügbar", + "NOT_PROVIDED": "Nicht vorgesehen", + "NOT_TO_DISCLOSE": "Nicht bekannt geben", + "NO_LIMIT": "Keine Begrenzung", + "NUMBER_OF_RECORDS": "Anzahl der Datensätze", + "OK": "OK", + "ONLY_DELETE_DATA": "Nur Daten löschen", + "ONLY_RESET_SETTING": "Nur Einstellung zurücksetzen", + "OPEN": "Öffnen", + "OPEN_URL": "Öffne URL", + "OPEN_WITH_...": "Öffnen mit...", + "OPTIMIZING_DATA_...": "Optimierung der Daten...", + "ORGANIZATION": "Organization", + "ORIGINAL": "Original", + "OTHERS": "Andere", + "PASSWORD": "Passwort", + "PATCH_NOTES": "Hinweise zum Patch", + "PERMISSION_REQUIRED": "Erlaubnis erforderlich", + "PERSONAL": "Persönlich", + "PHONE_NO": "Telefon Nr.", + "PHONE_NUMBER": "Telefon Nummer", + "PLEASE_WAIT": "Bitte warten", + "POSTAL_CODE": "Postleitzahl", + "PREPARING": "Vorbereiten", + "PREVIEW": "Vorschau", + "PRIVACY_POLICY": "Datenschutz", + "QR_CODE": "QR Code", + "QR_CODE_AND_DECODED_RESULT": "QR Code und entschlüsseltes Ergebnis", + "QR_CODE_CONTENT": "QR code Inhalt", + "QR_CODE_STYLE": "QR-Code-Stil", + "RATE_THE_APP": "Die App bewerten", + "RECORDS_LIMIT": "Aufzeichnungen Limit", + "REMOVE_ALL": "Entferne All", + "REMOVE_BCC": "Entferne BCC", + "REMOVE_CC": "Entferne CC", + "REMOVE_RECIPIENT": "Empfänger entfernen", + "REPORT_ISSUE": "Ticket erstellen", + "RESET_APP": "App zurücksetzen", + "RESET_DEFAULT": "Standard zurücksetzen", + "RESTORE": "Wiederherstellen", + "RESULT": "Ergebnis", + "SAVE": "Speichern", + "SAVED": "Gespeichert", + "SCAN": "Scannen", + "SCANNED": "Gescannt", + "SCANNING_FEEDBACK_ONLY": "Nur Feedback scannen", + "SCAN_BY_CAMERA": "Scannen mit der Kamera", + "SCREEN_ORIENTATION": "Bildschirm Ausrichtung", + "SEARCH": "Suche", + "SEARCH_ENGINE": "Suchmaschine", + "SECRET": "Geheimnis", + "SEND": "Sende", + "SEND_EMAIL": "Sende Email", + "SEND_MESSAGE": "Nachricht senden", + "SETTING": "Einstellung", + "SETTINGS": "Einstellungen", + "SHARE": "Teile", + "SHARING": "Teilen", + "SHOW": "Zeige", + "SHOW_NUMBER_OR_RECORDS": "Zeige die Anzahl der Datensätze an", + "SHOW_QR_CODE": "Zeige QR Code", + "SIMPLE_QR": "Simple QR", + "STATE": "Staat", + "STREET": "Straße", + "SUCCESS": "Erfolg", + "SUPPORTED_BARCODE_TYPE": "Unterstützter Barcode-Typ", + "SUPPORTED_TYPE": "Unterstützter Typ", + "SYSTEM_DEFAULT": "Systemvoreinstellung", + "TASK": "Aufgabe", + "TASK_BUTTON_LAYOUT": "Aufgaben-Schaltflächen-Layout", + "TURN_OFF": "Ausschalten", + "TURN_ON": "Anschalten", + "TURNED_OFF": "Ausgeschaltet", + "TURNED_ON": "Angeschaltet", + "TUTORIAL": "Anleitung", + "UNDO": "Rückgängig", + "UNKNOWN": "Unbekannt", + "UPDATE_SUCCESSFULLY": "Erfolgreich aktualisiert", + "URL": "URL", + "VCARD_CONTACT": "vCard Kontakt", + "VERSION_VERSION": "Version {version}", + "VIBRATION": "Vibration", + "VIEWED": "Gesehen", + "VIEW_BOOKMARK": "Lesezeichen ansehen", + "VIEW_GITHUB": "GitHub anzeigen", + "VIEW_LOG": "Protokoll anzeigen", + "VIEW_STORE_AND_SOURCE_CODE": "Store und Quellcode anzeigen", + "VIEW_INSTRUCTIONS": "Anweisungen anzeigen", + "WEBSITE": "Website", + "WIFI": "WiFi", + "WIFI_ENCRYPTION": "WiFi Verschlüsselung", + "WIFI_SSID": "WiFi SSID", + "WORK": "Arbeit", + "WORK_PHONE_NUMBER": "Telefonnummer bei der Arbeit", + "YAHOO_SEARCH": "Yahoo! Suche", + "YANDEX": "Yandex", + "YES": "Ja", + "MSG": { + "ALREADY_BOOKMARKED": "Fehlgeschlagen! Bereits zuvor mit Lesezeichen versehen", + "AUTO_KILL_BACKGROUND_EXPLAIN": "

Um Systemressourcen und Akku zu sparen, lege die Zeit fest, zu der die Anwendung automatisch beendet wird, wenn sie im Hintergrund läuft.


Wenn Du gewählt hast Systemeinstellung folgens, die Anwendung wird vollständig vom System kontrolliert und kann nicht von selbst beendet werden.


Bitte beachte, dass in manchen Situationen, könnte das System die Anwendung im Voraus stoppen.

", + "AUTO_LOGGING_EXPLAIN": "Jeder QR-Code und Barcode-Inhalt wird protokolliert und automatisch gespeichert, nachdem Du ihn gescannt, erstellt oder erneut angezeigt hast. Du kannst sie auf der Seite Log einsehen.", + "AUTO_MAX_BRIGHTNESS_EXPLAIN": "Die Bildschirmhelligkeit wird automatisch auf das Maximum eingestellt, wenn ein QR-Code angezeigt wird..", + "AUTO_OPEN_URL_EXPLAIN": "Wenn der QR-Code gescannt wird und der Inhalt eine URL ist, wird die URL automatisch geöffnet.", + "AUTO_SHOW_QR_EXPLAIN": "Nach den folgenden Aktionen wird automatisch ein QR-Code auf der Ergebnisseite angezeigt.", + "BACKUP_EXPLAIN": "Du kannst alle Datensätze und Lesezeichen lokal sichern. Nach der Sicherung erhältst du eine Reihe von Geheimnissen. Bewahre das Geheimnis sicher auf, sonst kannst du die Sicherung nicht wiederherstellen. Bitte beachte, dass Simple QR keine plattformübergreifende Sicherung und Wiederherstellung unterstützt.", + "BACKUP_FAILED": "Sicherung fehlgeschlagen", + "BACKUP_FAILED_2": "Die Sicherung konnte nicht durchgeführt werden. Bitte stelle sicher, dass die Speichererlaubnis erteilt wurde.", + "BACKUP_SUCCESSFULLY": "

Die Sicherung wurde erfolgreich durchgeführt. Bitte speichere die Sicherungsdatei und bewahre das folgende Geheimnis sicher auf

{secret}

", + "BARCODE_TYPE": "Scanne
  • QR Code
  • 1D Barcode
  • Aztec Code
  • Data Matrix
  • PDF417
Bild importieren
  • QR Code
Erstelle
  • QR Code
", + "BOOKMARKED": "Erfolgreich mit Lesezeichen versehen", + "BUTTON_DISPLAY_EXPLAIN": "Ein- und Ausblenden der Aufgabenschaltflächen.", + "BUTTON_STYLE_EXPLAIN": "Wähle den Stil der Aufgabenschaltflächen.", + "CAMERA_PERMISSION": "Um das Scannen zu aktivieren, muss man der Kamera die Erlaubnis erteilen.", + "CONTACT_PERMISSION": "Du musst Kontakt die Erlaubnis erteilen, den Kontakt zu speichern.", + "COPIED_SECRET": "Geheimnis kopiert", + "COPY_TEXT": "Welche Inhalte möchtest du kopieren?", + "CREATE_QRCODE_MAX_LENGTH": "Max. 1817 Zeichen", + "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "Die Länge des generierten QR-Code-Inhalts darf 1817 Zeichen nicht überschreiten.", + "DEBUG_MODE_ON": "Erfolgreich den Debug-Modus eingeschaltet", + "DELETE_OVERFLOWED_RECORDS": "Nach dem Verlassen dieser Seite werden die übergelaufenen Datensätze dauerhaft gelöscht.", + "EMAIL_MAX_LENGTH": "Max. 254 Zeichen", + "EMAIL_SUBJECT_MAX_LENGTH": "Max. 78 Zeichen", + "ERROR_CORRECTION_LEVEL_EXPLAIN": "

Der QR-Code verfügt über eine Fehlerkorrekturfunktion, die es ermöglicht, Daten wiederherzustellen, selbst wenn der Code beschädigt ist.


Es stehen 4 Stufen zur Verfügung:

Level L stellt 7 % der Datenbytes wieder her.

Level M stellt 15 % der Datenbytes wieder her.

Level Q stellt 25 % der Datenbytes wieder her.

Level H stellt 30 % der Datenbytes wieder her.


Bitte beachte, dass eine Erhöhung der Level die Fehlerkorrekturfähigkeit verbessern kann, aber auch die Größe des QR-Codes erhöht. Daher wird für allgemeine Fälle die Level M empfohlen.

", + "EXIT_APP": "

Bist du sicher, dass du die App beenden willst?

Wenn dir Simple QR gefällt, bewerte es bitte im Store.

", + "EXPORT_TO_CSV_EXPLAIN": "Sie können alle Datensätze und Lesezeichen in eine CSV-Datei exportieren.", + "FAILED_SAVING_CONTACT": "Kontakt konnte nicht gespeichert werden", + "FAIL_PREPARE_SMS": "Nachricht konnte nicht gesendet werden", + "IMPORT_FAILED": "Importieren fehlgeschlagen", + "IMPORT_FROM_CSV_EXPLAIN": "Sie können Datensätze und Lesezeichen aus einer CSV-Datei mit dem von Simple QR definierten Format importieren. Wenn Sie Daten zwischen Android und iOS übertragen möchten, verwenden Sie bitte diese Funktion.", + "INPUT_TAG": "Bitte gib dem Lesezeichen einen Tag", + "INVALID_BK_FILE": "Dies ist keine gültige Sicherungsdatei.", + "INVALID_CSV_FILE": "Dies ist keine gültige CSV-Datei.", + "NOT_BASE64_DE": "Daten können nicht Base64-dekodiert werden", + "NOT_BASE64_EN": "Daten können nicht Base64-kodiert werden", + "NOT_BASE64_EN_DE": "Daten können nicht mit Base64 kodiert und dekodiert werden", + "NO_QR_CODE": "Kann QR-Code nicht erkennen", + "ONLY_VCARD_3_0": "Bitte gültigen Code eingeben", + "PLEASE_INPUT_VALID_SECRET": "Bitte gültigen Code eingeben", + "PORTRAIT_ONLY": "Gesperrtes Porträt", + "PREPARE_SMS": "Vorbereiten der Nachricht", + "PREVIOUS_RECORDS": "Frühere Aufzeichnungen vom Scannen, Erstellen oder Anzeigen", + "QR_CODE_VALUE_NOT_EMPTY": "QR Code Wert darf nicht leer sein", + "READ_IMAGE_PERMISSION": "Du musst eine Speichererlaubnis für das Scannen von Bildern erteilen.", + "RECORDS_LIMIT_EXPLAIN": "Begrenzen Sie die Anzahl der Datensätze, die gespeichert werden können. Der älteste Datensatz wird gelöscht, wenn das Limit erreicht ist.", + "REMOVE_ALL_BOOKMARKS": "Bist du sicher, dass du alle mit Lesezeichen versehenen Texte entfernen willst? Diese Änderung kann nicht rückgängig gemacht werden.", + "REMOVE_ALL_RECORD": "Bist du sicher, dass du alle Datensätze entfernen willst? Diese Änderung kann nicht rückgängig gemacht werden.", + "RESET_APP": "Bist du sicher, dass du diese App zurücksetzen und alle Daten löschen willst? Diese Änderung kann nicht rückgängig gemacht werden.", + "RESET_DEFAULT": "Sind Sie sicher, auf die Standardwerte zurückzusetzen?", + "RESTORE_EXPLAIN": "Du kannst eine frühere Sicherung von Datensätzen und Lesezeichen wiederherstellen. Die Elemente aus der Sicherung werden mit den vorhandenen Datensätzen zusammengeführt. Der Dateityp der Sicherung sollte TFSQBK sein.", + "RESTORE_EXPLAIN_IOS": "Du kannst eine frühere Sicherung von Datensätzen und Lesezeichen wiederherstellen. Die Elemente aus der Sicherung werden mit den vorhandenen Datensätzen zusammengeführt. Der Dateityp der Sicherung sollte ISQBK sein.", + "RESTORE_FAILED": "Wiederherstellung fehlgeschlagen", + "RESTORE_SECRET": "Bitte gib dein Schlüssel ein, um die Datensätze wiederherzustellen", + "RESTORE_SUCCESSFUL": "Erfolgreich wiederhergestellt", + "RESTORE_WRONG_SECRET": "Fehlerhafter Code", + "SAVED_CONTACT": "Gespeicherter Kontakt", + "SAVING_CONTACT": "Kontakt speichern", + "SCAN_QR_FROM_IMAGE": "Simple QR benutzt \"cozmo/jsQR\" Modul zum Scannen von QR-Codes aus Bildern. Um erfolgreich zu scannen,", + "SCAN_QR_FROM_IMAGE_R1": "Bitte gestatte der App den Zugriff auf den Speicher oder die Fotobibliothek.", + "SCAN_QR_FROM_IMAGE_R2": "Achte darauf, dass der Hintergrund des ausgewählten Bildes nicht transparent ist. Es wird empfohlen, ein Bild zu wählen, das nicht von der Kamera aufgenommen wurde.", + "SCAN_QR_FROM_IMAGE_R3": "Bitte brich die Fotobearbeitung ab, wenn du das gesamte Bild scannen möchtest. (falls zutreffend)", + "SEARCH": "Nach welchen Inhalten möchtest du suchen?", + "SEARCH_ENGINE_EXPLAIN": "Wähle die Suchmaschine für die Websuche, nachdem du das Ergebnis erhalten hast.", + "SHARE_QR": "Einfach scannen!\n\nGeteilt von Simple QR", + "SMS_MAX_LENGTH": "Max. 160 Zeichen", + "SSID_MAX_LENGTH": "Max. 32 Zeichen", + "START_PAGE_EXPLAIN": "Wähle die Startseite der App.", + "START_PAGE_HEADER_EXPLAIN": "Einfache QR-Kopfzeile auf der Startseite anzeigen.", + "TAG_MAX_LENGTH": "Max. 30 Zeichen.", + "TAG_MAX_LENGTH_EXPLAIN": "Die Länge des Tags darf 30 Zeichen nicht überschreiten.", + "TUTORIAL_NOT_SHOW_AGAIN": "Nicht wieder zeigen", + "TUTORIAL_SWIPE_LEFT": "Wische nach links, um den entsprechenden Datensatz zu löschen.", + "TUTORIAL_SWIPE_RIGHT": "Wische nach rechts, um den Text des entsprechenden Datensatzes mit einem Lesezeichen zu versehen / Bearbeiten Sie das Label des entsprechenden Lesezeichens.", + "UNDO_DELETE": "Du kannst die Löschung rückgängig machen", + "VIBRATION_EXPLAIN": "Vibration oder haptisches Feedback geben. Bitte beachte, dass nicht alle Geräte diese Funktion unterstützen." + }, + "BARCODE_TYPE": { + "1D": "1D Barcode ", + "AZTEC": "Aztec Code ", + "DATA_MATRIX": "Data Matrix ", + "EAN": "Europäische Artikelnummer ", + "MAXICODE": "MaxiCode ", + "PDF_417": "PDF417 ", + "QR_CODE": "QR Code ", + "RSS": "GS1 DataBar ", + "UPC": "Universeller Produktcode " + }, + "UPDATE": { + "UPDATE_NOTES_ANDROID": "

Diese Version bringt Ihnen mehrere Updates und neue Funktionen. Bitte überprüfen Sie GitHub für Details.

", + "UPDATE_NOTES_IOS": "

Diese Version bringt Ihnen mehrere Updates und neue Funktionen. Bitte überprüfen Sie GitHub für Details.

" + } +} \ No newline at end of file diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 4477819..5bb0dd3 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1,275 +1,322 @@ { - "SIMPLE_QR": "Simple QR", - "MESSAGE": "Message", - "UPDATE_NOTES": "Patch Notes", - "PERMISSION_REQUIRED": "Permission Required", - "CAMERA_PAUSED": "Camera Paused", - "CREATE_QRCODE": "Create QR Code", - "CREATE_QRCODE_MAX_LENGTH": "Max. 1817 characters", - "QRCODE_CONTENT": "QR code content", - "CONTENT": "Content", - "EXIT_APP": "Exit App", - "WEBSITE": "Website", - "PHONE_CALL": "Phone Call", - "SEARCH": "Search", - "COPY": "Copy", - "OPEN": "Open", - "YES": "Yes", - "NO": "No", - "OK": "OK", + "100_RECORDS": "100 Records", + "30_RECORDS": "30 Records", + "49_DIGIT": "49-digit", + "50_RECORDS": "50 Records", "ABOUT": "About", - "ENTER": "Enter", - "CONFIRM": "Confirm", - "CANCEL": "Cancel", - "RESUME": "Resume", - "ORIGINAL": "Original", - "BASE64": "Base64", - "BASE64_ENCODED": "Base64 Encoded", - "BASE64_DECODED": "Base64 Decoded", - "HISTORY": "History", - "TUTORIAL": "Tutorial", - "TUTORIAL_TOUCH_HISTORY": "Touch this to view all scanning history.", - "TUTORIAL_TOUCH_BOOKMARK": "Touch this to view all bookmarks.", - "TUTORIAL_SWIPE_LEFT": "Swipe left to delete corresponding record.", - "TUTORIAL_SWIPE_RIGHT": "Swipe right to bookmark the text of corresponding record.", - "TUTORIAL_NOT_SHOW_AGAIN": "Do not show again", - "BOOKMARKS": "Bookmarks", - "SCANNING_HISTORY_SHOWN": "Scanning history will be shown here", - "FAVOURITE_TEXT_SHOWN": "Bookmarked texts will be shown here", - "REMOVE_ALL": "Remove All", - "RESULT": "Result", - "UNDO": "Undo", - "PHONE_NUMBER": "Phone Number", - "MESSAGE_CONTENT": "Message Content", - "EMAIL_TO": "Email to", - "CC": "CC", - "BCC": "BCC", - "EMAIL_SUBJECT": "Subject", - "EMAIL_BODY": "Body", - "CONTACT_NAME": "Contact Name", - "BARCODE_CONTENT": "Barcode Content", - "BARCODE_FORMAT": "Barcode Format", - "BROWSE_WEBSITE": "Browse Website", - "ADD_CONTACT": "Add Contact", - "CALL": "Call", - "SEND_MESSAGE": "Send Message", - "SEND_EMAIL": "Send Email", - "CONNECT_WIFI": "Connect to WiFi", - "WIFI_NETWORK": "WiFi Network", - "CONNECT": "Connect", - "WIFI_SSID": "WiFi SSID", - "WIFI_ENCRYPTION": "Encryption", - "WIFI_HIDDEN": "Hidden Network?", - "NOT_AVAILABLE": "Not Available", - "NOT_PROVIDED": "(Not Provided)", - "WEB_SEARCH": "Search", - "COPY_TEXT": "Copy Text", - "SHARE_QRCODE": "Share", - "SETTING": "Setting", - "SETTINGS": "Settings", - "LANGUAGE": "Language", - "SYSTEM_DEFAULT": "System Default", - "COLOR_THEME": "Color Theme", - "LIGHT": "Light", - "DARK": "Dark", - "BLACK": "Black", - "CAMERA_PAUSE_TRIGGER": "Camera Pause Trigger", - "NEVER": "Never", - "SECONDS": "seconds", - "CAMERA_PAUSE_EXPLAIN": "When scanning, the app keeps detecting the motion of your device. If the device is motionless for a time, the scanning and camera preview will be paused to save battery. Choose Never to disable the function.", - "LOGGING_BACKUP_RESTORE": "Logging, Backup & Restore", - "TURN_ON": "On", - "TURN_OFF": "Off", - "AUTO_LOGGING": "Auto Logging", - "AUTO_LOGGING_EXPLAIN": "Every QR code and barcode content are logged and stored automatically after you scan, create or view it again. You can view them on History page.", - "BACKUP_AND_RESTORE": "Backup & Restore", - "BACKUP": "Backup", - "RESTORE": "Restore", - "BACKUP_EXPLAIN": "You can back up all history and bookmarks locally. You will be given a set of secret after backing up. Please keep the secret securely, otherwise you cannot restore the backup. Please also be reminded that Simple QR does not support cross-platform backup and restore. You must grant the storage permission.", - "RESTORE_EXPLAIN": "You can restore a previous backup of history and bookmarks. The items from the backup will be merged to the existing records. The backup file type should be TFSQBK.", - "RESTORE_EXPLAIN_IOS": "You can restore a previous backup of history and bookmarks. The items from the backup will be merged to the existing records. The backup file type should be ISQBK.", - "COPY_AND_BACKUP": "Copy & Back Up", - "ENCRYPTING": "Encrypting...", - "BACKING_UP": "Backing Up...", - "SECRET": "Secret", - "LENGTH_49": "49-digit", - "LENGTH_32": "32-digit", - "LENGTH_16": "16-digit", - "COPY_SECRET": "Just Copy Secret", - "COPY_SECRET_SHARE": "Copy Secret & Save Backup", - "TURN_ON_HAPTIC": "Haptic Feedback Only", - "TURN_ON_SCANNED": "Scanning Feedback Only", - "SEARCH_ENGINE": "Search Engine", - "GOOGLE": "Google Search", - "BING": "Microsoft Bing", - "YAHOO": "Yahoo! Search", - "DUCK_DUCK_GO": "DuckDuckGo", - "SEARCG_ENGINE_EXPLAIN": "Set the search engine for doing web search in the Result page.", "ABOUT_SIMPLE_QR": "About Simple QR", - "CHECK_GOOGLE_PLAY": "View Google Play", - "CHECK_APP_STORE": "View App Store", + "ADD": "Add", + "ADD_BCC": "Add BCC", + "ADD_CC": "Add CC", + "ADD_CONTACT": "Add Contact", + "ADD_RECIPIENT": "Add Recipient", + "APP": "App", + "APPEARANCE_AND_EFFECTS": "Appearance & Effects", + "APP_INITIAL_PAGE": "App Initial Page", + "APP_VERSION": "App Version", + "AT": "at", + "AT_LEAST_1_MINUTE_LATER": "At Least 1 Minute Later", + "AT_LEAST_3_MINUTES_LATER": "At Least 3 Minutes Later", + "AT_LEAST_5_MINUTES_LATER": "At Least 5 Minutes Later", + "AUTO_KILL_BACKGROUND": "Auto Kill Background", + "AUTO_LOGGING": "Auto Logging", + "AUTO_MAX_BRIGHTNESS": "Auto Max Brightness", + "AUTO_OPEN_URL": "Auto Open URL", + "AUTO_QR_CODE_POPUP": "Auto QR Code Pop-up", + "BACKGROUND_COLOR": "Background Color", + "BACKING_UP": "Backing Up", + "BACKUP": "Backup", + "BASE64": "Base64", + "BASE64_DECODED": "Base64 Decoded", + "BASE64_ENCODED": "Base64 Encoded", + "BCC": "BCC", + "BLACK": "Black", + "BOOKMARK": "Bookmark", + "BOOKMARKED": "Bookmarked", + "BOOKMARKED_TEXTS": "Bookmarked texts", + "BOOKMARKS": "Bookmarks", + "BRAVE_SEARCH": "Brave Search", + "BROWSE": "Browse", + "BROWSER": "Browser", + "BROWSE_WEBSITE": "Browse Website", + "CALL": "Call", + "CANCEL": "Cancel", + "CC": "CC", + "CITY": "City", + "CLEAR": "Clear", + "CLOSE": "Close", + "COLOR": "Color", + "COLOR_THEME": "Color Theme", + "COMING_SOON": "Coming soon", + "CONTACT_METHOD": "Contact Method", + "CONTACT_NAME": "Contact Name", + "CONTENT": "Content", + "CONTENT_TYPE": "Content Type", + "COPIED": "Copied", + "COPY": "Copy", + "COPY_SECRET_AND_SAVE_BACKUP": "Copy Secret & Save Backup", + "COPY_TEXT": "Copy Text", + "COUNTRY": "Country", + "CREATE": "Create", + "CREATED": "Created", + "CREATE_QR_CODE": "Create QR Code", + "DARK": "Dark", + "DATE_OF_BIRTH": "Date of Birth", + "DEBUG_MODE": "Debug Mode", + "DECODING": "Decoding", + "DECRYPTING": "Decrypting", + "DETAILED": "Detailed", + "DONE": "Done", + "DUCK_DUCK_GO": "DuckDuckGo", + "ECOSIA": "Ecosia", + "EDIT": "Edit", + "EMAIL_ADDRESS": "Email Address", + "EMAIL_BODY": "Email Body", + "EMAIL_NTT_DOCOMO": "Email (NTT Docomo)", + "EMAIL_RECIPIENT": "Email Recipient", + "EMAIL_SUBJECT": "Email Subject", + "EMAIL_TO": "Email to", + "EMAIL_W3C_STANDARD": "Email (W3C Standard)", + "ENCRYPTING": "Encrypting", + "ERROR_CORRECTION_LEVEL": "Error Correction Level", + "EXIT": "Exit", + "EXIT_APP": "Exit App", + "EXPORT": "Export", + "EXPORTING": "Exporting", + "EXPORT_TO_CSV": "Export to CSV", + "FAX_NUMBER": "Fax Number", + "FEMALE": "Female", + "FIRST_NAME": "First Name", + "FOLLOW_SYSTEM_SETTINGS": "Follow System Settings", + "FREE_TEXT": "Free Text", + "FULL_RESET": "Full Reset", + "FUNCTIONS": "Functions", + "GENDER": "Gender", + "GEOLOCATION": "Geolocation", + "GOOGLE_SEARCH": "Google Search", + "HAPTIC_FEEDBACK_ONLY": "Haptic Feedback Only", + "HIDDEN_NETWORK_?": "Hidden Network?", + "HOME_ADDRESS": "Home Address", + "HOME_PHONE_NUMBER": "Home Phone Number", + "ICON_ONLY": "Icon Only", + "IMPORT": "Import", + "IMPORT_FROM_CSV": "Import from CSV", + "IMPORT_IMAGE": "Import Image", + "INITIAL_SEGMENT": "Initial Segment", + "JOB_TITLE": "Job Title", + "LANGUAGE": "Language", + "LAST_NAME": "Last Name", + "LATITUDE": "Latitude", + "LEVEL_H": "Level H", + "LEVEL_L": "Level L", + "LEVEL_M": "Level M", + "LEVEL_Q": "Level Q", + "LIGHT": "Light", + "LOADING_DATA": "Loading data", + "LOCK_LANDSCAPE": "Lock Landscape", + "LOCK_PORTRAIT": "Lock Portrait", + "LOG": "Log", + "LOG_BACKUP_AND_RESTORE": "Log, Backup & Restore", + "LONGITUDE": "Longitude", + "MALE": "Male", + "MANAGE_RECORDS": "Manage Records", + "MARGIN": "Margin", + "MESSAGE": "Message", + "MESSAGE_CONTENT": "Message Content", + "MICROSOFT_BING": "Microsoft Bing", + "MOBILE_PHONE_NUMBER": "Mobile Phone Number", + "MORE": "More", + "NAME": "Name", + "NO": "No", + "NONE": "None", + "NOT_AVAILABLE": "Not Available", + "NOT_PROVIDED": "Not Provided", + "NOT_TO_DISCLOSE": "Not to disclose", + "NO_LIMIT": "No Limit", + "NUMBER_OF_RECORDS": "Number of Records", + "OK": "OK", + "ONLY_DELETE_DATA": "Only Delete Data", + "ONLY_RESET_SETTING": "Only Reset Setting", + "OPEN": "Open", + "OPEN_URL": "Open URL", + "OPEN_WITH_...": "Open with...", + "OPTIMIZING_DATA_...": "Optimizing data...", + "ORGANIZATION": "Organization", + "ORIGINAL": "Original", + "OTHERS": "Others", + "PASSWORD": "Password", + "PATCH_NOTES": "Patch Notes", + "PERMISSION_REQUIRED": "Permission Required", + "PERSONAL": "Personal", + "PHONE_NO": "Phone No.", + "PHONE_NUMBER": "Phone Number", + "PLEASE_WAIT": "Please wait", + "POSTAL_CODE": "Postal Code", + "PREPARING": "Preparing", + "PREVIEW": "Preview", "PRIVACY_POLICY": "Privacy Policy", - "SUPPORT_DEVELOPER": "Support Developer", - "WATCH_ADS": "Watch an Ad", - "THANKS_SUPPORT": "Loading Ad... Thanks for support", - "DONATE_PAYPAL": "Donate via PayPal", - "SEE_MORE": "See More", - "SUCCESS": "Success", - "RESTORED": "Restored", - "SUPPORT_DEV_EXPLAIN": "Simple QR is a free and open-source app. If you want to support me and my projects, please feel free to contribute to the projects!", - "DEVELOPING": "Developing", + "QR_CODE": "QR Code", + "QR_CODE_AND_DECODED_RESULT": "QR Code and Decoded Result", + "QR_CODE_CONTENT": "QR Code Content", + "QR_CODE_STYLE": "QR Code Style", + "RATE_THE_APP": "Rate the App", + "RECORDS_LIMIT": "Records Limit", + "REMOVE_ALL": "Remove All", + "REMOVE_BCC": "Remove BCC", + "REMOVE_CC": "Remove CC", + "REMOVE_RECIPIENT": "Remove Recipient", "REPORT_ISSUE": "Report Issue", "RESET_APP": "Reset App", - "APP_VERSION": "App Version", - "NEW_CONTACT": "New Contact", + "RESET_DEFAULT": "Reset Default", + "RESTORE": "Restore", + "RESULT": "Result", "SAVE": "Save", - "REQUIRED": "Required", - "NEW_CONTACT_TIP": "Only name, phone number and email address can be saved.", - "FIRST_NAME": "First Name", - "LAST_NAME": "Last Name", - "PHONE_TAG": "Phone Tag", - "EMAIL_ADDRESS": "Email Address", - "EMAIL_TAG": "Email Tag", - "DEFAULT_OR_OTHER": "Default / Other", - "MOBILE": "Mobile", - "HOME": "Home", - "WORK": "Work", - "PREPARING": "Preparing", - "PLEASE_WAIT": "Please wait...", - "DECODING": "Decoding...", - "CHECK_PERMISSION": "Checking Permission", - "CHECK_WIFI": "Checking WiFi On/Off", - "RECOGNIZE_NETWORK": "Recognizing", - "CONNECTING_NETWORK": "Connecting", - "CONTENT_TYPE": "Content Type", - "FREE_TEXT": "Free Text", - "URL": "URL", - "VCARD_CONTACT": "vCard Contact", - "PHONE": "Phone", - "SMS": "Message", - "EMAIL": "Email", - "WIFI": "WiFi", - "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "The length of generated QR Code content must not exceed 1817 characters", - "CREATE": "Create", - "CLEAR": "Clear", - "EMAIL_RECIPIENT": "Email Recipient", - "EMAIL_MAX_LENGTH": "Max. 254 characters", - "ADD_RECIPIENT": "Add Recipient", - "REMOVE_RECIPIENT": "Remove Recipient", - "ADD_CC": "Add CC", - "REMOVE_CC": "Remove CC", - "ADD_BCC": "Add BCC", - "REMOVE_BCC": "Remove BCC", - "EMAIL_SUBJECT_MAX_LENGTH": "Max. 78 characters", - "SMS_MAX_LENGTH": "Max. 160 characters", - "NOT_TO_DISCLOSE": "Not to disclose", - "MALE": "Male", - "FEMALE": "Female", - "NAME": "Name", - "CONTACT_METHOD": "Contact Method", - "MOBILE_PHONE_NUMBER": "Mobile Phone Number", - "HOME_PHONE_NUMBER": "Home Phone Number", - "WORK_PHONE_NUMBER": "Work Phone Number", - "FAX_NUMBER": "Fax Number", - "ORGANIZATION": "Organization", - "JOB_TITLE": "Job Title", - "HOME_ADDRESS": "Home Address", - "STREET": "Street", - "CITY": "City", - "STATE": "State", - "POSTAL_CODE": "Postal Code", - "COUNTRY": "Country", - "PERSONAL": "Personal", - "BIRTHDAY": "Date of Birth", - "GENDER": "Gender", - "SSID_MAX_LENGTH": "Max. 32 characters", - "NONE": "None", - "PASSWORD": "Password", + "SAVED": "Saved", "SCAN": "Scan", - "IMPORT": "Import", - "IMPORT_IMAGE": "Import Image", - "CHECK_MODULE": "Check Module", - "VIEW_SOURCE": "View Source", - "TURN_ON_TORCH": "Turn on Torch", - "TURN_OFF_TORCH": "Turn off Torch", - "VIBRATION": "Vibration", - "CLOSE": "Close", + "SCANNED": "Scanned", + "SCANNING_FEEDBACK_ONLY": "Scanning Feedback Only", + "SCAN_BY_CAMERA": "Scan by Camera", + "SCREEN_ORIENTATION": "Screen Orientation", + "SEARCH": "Search", + "SEARCH_ENGINE": "Search Engine", + "SECRET": "Secret", + "SEND": "Send", + "SEND_EMAIL": "Send Email", + "SEND_MESSAGE": "Send Message", + "SETTING": "Setting", + "SETTINGS": "Settings", + "SHARE": "Share", + "SHARING": "Sharing", + "SHOW": "Show", + "SHOW_NUMBER_OR_RECORDS": "Show Number of Records", + "SHOW_QR_CODE": "Show QR Code", + "SIMPLE_QR": "Simple QR", + "STATE": "State", + "STREET": "Street", + "SUCCESS": "Success", "SUPPORTED_BARCODE_TYPE": "Supported Barcode Type", "SUPPORTED_TYPE": "Supported Type", - "DEBUG_MODE": "Debug Mode", + "SYSTEM_DEFAULT": "System Default", + "TASK": "Task", + "TASK_BUTTON_LAYOUT": "Task Button Layout", + "TURN_OFF": "Turn Off", + "TURN_ON": "Turn On", + "TURNED_OFF": "Turned Off", + "TURNED_ON": "Turned On", + "TUTORIAL": "Tutorial", + "UNDO": "Undo", + "UNKNOWN": "Unknown", + "UPDATE_SUCCESSFULLY": "Update Successfully", + "URL": "URL", + "VCARD_CONTACT": "vCard Contact", + "VERSION_VERSION": "Version {version}", + "VIBRATION": "Vibration", + "VIEWED": "Viewed", + "VIEW_BOOKMARK": "View Bookmark", + "VIEW_GITHUB": "View GitHub", + "VIEW_LOG": "View Log", + "VIEW_STORE_AND_SOURCE_CODE": "View Store & Source Code", + "VIEW_INSTRUCTIONS": "View Instructions", + "WEBSITE": "Website", + "WIFI": "WiFi", + "WIFI_ENCRYPTION": "WiFi Encryption", + "WIFI_SSID": "WiFi SSID", + "WORK": "Work", + "WORK_PHONE_NUMBER": "Work Phone Number", + "YAHOO_SEARCH": "Yahoo! Search", + "YANDEX": "Yandex", + "YES": "Yes", "MSG": { - "CALL_PHONE": "Are you sure to call {phoneNumber} now?", - "FAIL_CALL_PHONE": "Failed to open dialer", - "PREPARE_SMS": "Preparing message", - "FAIL_PREPARE_SMS": "Failed to send message", - "SEARCH": "Which content do you want to search for?", - "COPY_TEXT": "Which content do you want to copy?", - "COPIED": "Copied", - "DECODED": "Decoded", - "NOT_BASE64_EN_DE": "Data cannot be Base64 encoded and decoded", - "NOT_BASE64_EN": "Data cannot be Base64 encoded", - "NOT_BASE64_DE": "Data cannot be Base64 decoded", - "SHARE_QR": "Just scan it!\n\nShared by Simple QR", - "SAVED": "Saved", - "EXIT_APP": "Do you want to leave now?", - "CAMERA_PERMISSION_1": "Press Setting to grant Camera permission.", - "CAMERA_PERMISSION_2": "You must grant Camera permission for scanning.", - "READ_IMAGE_PERMISSION": "You must grant Storage permission for scanning image.", - "CAMERA_PAUSED": "The camera is paused to save battery. To resume, hold the device or press Resume.

The function can be configured in Setting.", - "UNDO_DELETE": "You can undo the deletion", - "REMOVE_ALL_RECORD": "Are you sure to remove all records? This action cannot be undone.", - "GIVE_NAME_CONTACT": "Give a name to the contact", - "FAIL_START_ADS": "Failed to start ad", - "FAIL_LOAD_ADS": "Failed to load ad", - "FAIL_SHOW_ADS": "Failed to show ad", - "ALREADY_BOOKMARKED": "Already Bookmarked", - "BOOKMARKED": "Bookmarked", - "UNBOOKMARKED": "Removed", - "RESET_APP": "Are you sure to reset this app and delete all data? This action cannot be undone.", - "REMOVE_ALL_BOOKMARKS": "Are you sure to remove all bookmarked texts? This action cannot be undone.", - "WIFI_NO_SSID": "SSID is not provided", - "WIFI_PERMISSION": "Please grant permission for WiFi connection", - "TURN_ON_WIFI": "Please turn on WiFi", - "TURN_ON_LOCATION": "Please turn on Location", - "WIFI_NOT_FOUND": "Your position is not inside this network coverage", - "FAIL_CONNECT_WIFI": "Failed to connect to this network", - "WIFI_NO_INTERNET": "Network has no internet access", - "ANDROID_WIFI_NATIVE_SETTINGS": "According to Android policies, we suggest you use native Settings to scan the QR code for adding the network.", - "QR_CODE_VALUE_NOT_EMPTY": "QR Code value cannot be empty", - "NO_QR_CODE": "No QR Code. Make sure that the image background is not transparent.", - "IMAGE_NO_TRANSPARENT": "Please make sure that the image background is not transparent.", - "SCAN_QR_FROM_IMAGE": "Simple QR uses \"cozmo/jsQR\" module to scan QR code from image. Please make sure that the background of the imported image is not transparent, and please also authorize Simple QR to access storage.", - "BARCODE_TYPE": "Scan
  • QR Code
  • 1D Barcode
  • Aztec Code
  • Data Matrix
  • PDF417
Import Image
  • QR Code
Create
  • QR Code
", - "TAP_TO_ZOOM_IN": "Tap to zoom in", - "ONLY_VCARD_3_0": "Only vCard 3.0 is supported", - "DEBUG_MODE_ON": "Successfully turned on Debug Mode", + "ALREADY_BOOKMARKED": "Failed! Already bookmarked before", + "AUTO_KILL_BACKGROUND_EXPLAIN": "

To save system resources and battery, set the time to automatically kill the app when it is running in the background.


If you choose Follow System Settings, the app will be fully controlled by system and won't be killed by itself.


Please note that in some situations, the system might stop the app in advance.

", + "AUTO_LOGGING_EXPLAIN": "Every QR code and barcode content are logged and stored automatically after you scan, create or view it again. You can view them on the Log page.", + "AUTO_MAX_BRIGHTNESS_EXPLAIN": "The screen brightness is automatically adjusted to the maximum when displaying QR code.", + "AUTO_OPEN_URL_EXPLAIN": "When the QR code is scanned and the content is a URL, the URL will be opened automatically.", + "AUTO_SHOW_QR_EXPLAIN": "Automatically pop up QR code on the Result page after the following actions.", + "BACKUP_EXPLAIN": "You can back up all records and bookmarks locally. You will be given a set of secret after backing up. Keep the secret securely, otherwise you cannot restore the backup. Please note that Simple QR does not support cross-platform backup and restore.", "BACKUP_FAILED": "Failed to back up", "BACKUP_FAILED_2": "Failed to back up. Please make sure that storage permission has been granted.", "BACKUP_SUCCESSFULLY": "

Successfully backed up. Please save the backup file and keep the following secret securely

{secret}

", - "RESTORE_SECRET": "Please input the secret to restore the records", - "INVALID_BK_FILE": "This is not a valid backup file.", - "RESTORE_FAILED": "Failed to restore", - "PLEASE_INPUT_VALID_SECRET": "Please input valid code", - "RESTORE_WRONG_SECRET": "Incorrect code", - "RESTORE_SUCCESSFUL": "Restored successfully", + "BARCODE_TYPE": "Scan
  • QR Code
  • 1D Barcode
  • Aztec Code
  • Data Matrix
  • PDF417
Import Image
  • QR Code
Create
  • QR Code
", + "BOOKMARKED": "Bookmarked Successfully", + "BUTTON_DISPLAY_EXPLAIN": "Show or hide the task buttons.", + "BUTTON_STYLE_EXPLAIN": "Choose the style of the task buttons.", + "CAMERA_PERMISSION": "To enable scanning, you must grant Camera permission.", + "CONTACT_PERMISSION": "You must grant Contact permission for saving contact.", "COPIED_SECRET": "Copied secret", - "SAVED_CONTACT": "Saved contact", + "COPY_TEXT": "Which content do you want to copy?", + "CREATE_QRCODE_MAX_LENGTH": "Max. 1817 characters", + "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "The length of generated QR Code content must not exceed 1817 characters.", + "DEBUG_MODE_ON": "Successfully turned on Debug Mode", + "DELETE_OVERFLOWED_RECORDS": "After leaving this page, the overflowed records will be permanently deleted.", + "EMAIL_MAX_LENGTH": "Max. 254 characters", + "EMAIL_SUBJECT_MAX_LENGTH": "Max. 78 characters", + "ERROR_CORRECTION_LEVEL_EXPLAIN": "

QR code has error correction capability to restore data even if the code is damaged.


There are 4 levels available:

Level L restores 7% of data bytes.

Level M restores 15% of data bytes.

Level Q restores 25% of data bytes.

Level H restores 30% of data bytes.


Please be reminded that raising the level can improve error correction capability but also increases the QR code size. Therefore, for general cases, level M is recommended.

", + "EXIT_APP": "

Are you sure to exit the app?

If you like Simple QR, please rate it on the store.

", + "EXPORT_TO_CSV_EXPLAIN": "You can export all records and bookmarks to a CSV file.", "FAILED_SAVING_CONTACT": "Failed to save contact", - "CONTACT_PERMISSION": "You must grant Contact permission for saving contact." + "FAIL_PREPARE_SMS": "Failed to send message", + "IMPORT_FAILED": "Failed to import", + "IMPORT_FROM_CSV_EXPLAIN": "You can import records and bookmarks from a CSV file with the format defined by Simple QR. If you want to transfer data between Android and iOS, please use this function.", + "INPUT_TAG": "Please give a tag to the bookmark", + "INVALID_BK_FILE": "This is not a valid backup file.", + "INVALID_CSV_FILE": "This is not a valid CSV file.", + "NOT_BASE64_DE": "Data cannot be Base64 decoded", + "NOT_BASE64_EN": "Data cannot be Base64 encoded", + "NOT_BASE64_EN_DE": "Data cannot be Base64 encoded and decoded", + "NO_QR_CODE": "Can't detect QR Code", + "ONLY_VCARD_3_0": "Only vCard 3.0 is supported", + "PLEASE_INPUT_VALID_SECRET": "Please input valid code", + "PORTRAIT_ONLY": "Locked portrait", + "PREPARE_SMS": "Preparing message", + "PREVIOUS_RECORDS": "Previous records from scanning, creating or viewing", + "QR_CODE_VALUE_NOT_EMPTY": "QR Code value cannot be empty", + "READ_IMAGE_PERMISSION": "You must grant Storage permission for scanning image.", + "RECORDS_LIMIT_EXPLAIN": "Limit the number of records that can be stored. The oldest record will be deleted when the limit is reached.", + "REMOVE_ALL_BOOKMARKS": "Are you sure to remove all bookmarked texts? This action cannot be undone.", + "REMOVE_ALL_RECORD": "Are you sure to remove all records? This action cannot be undone.", + "RESET_APP": "Are you sure to reset this app and delete all data? This action cannot be undone.", + "RESET_DEFAULT": "Are you sure to reset to default?", + "RESTORE_EXPLAIN": "You can restore a previous backup of records and bookmarks. The items from the backup will be merged to the existing records. The backup file type should be TFSQBK.", + "RESTORE_EXPLAIN_IOS": "You can restore a previous backup of records and bookmarks. The items from the backup will be merged to the existing records. The backup file type should be ISQBK.", + "RESTORE_FAILED": "Failed to restore", + "RESTORE_SECRET": "Please input the secret to restore the records", + "RESTORE_SUCCESSFUL": "Restored successfully", + "RESTORE_WRONG_SECRET": "Incorrect code", + "SAVED_CONTACT": "Saved contact", + "SAVING_CONTACT": "Saving contact", + "SCAN_QR_FROM_IMAGE": "Simple QR uses \"cozmo/jsQR\" module to scan QR code from image. To scan successfully,", + "SCAN_QR_FROM_IMAGE_R1": "Please authorize the app to access Storage or Photo Library.", + "SCAN_QR_FROM_IMAGE_R2": "Please ensure that the background of the selected image is not transparent. It is recommended to choose a picture that is not captured by the camera.", + "SCAN_QR_FROM_IMAGE_R3": "Please cancel photo editing if you want to scan the full image. (if applicable)", + "SEARCH": "Which content do you want to search for?", + "SEARCH_ENGINE_EXPLAIN": "Choose the search engine for doing web search after getting result.", + "SHARE_QR": "Just scan it!\n\nShared by Simple QR", + "SMS_MAX_LENGTH": "Max. 160 characters", + "SSID_MAX_LENGTH": "Max. 32 characters", + "START_PAGE_EXPLAIN": "Choose the initial page of the app.", + "START_PAGE_HEADER_EXPLAIN": "Show Simple QR header on the initial page.", + "TAG_MAX_LENGTH": "Max. 30 characters", + "TAG_MAX_LENGTH_EXPLAIN": "The length of the tag must not exceed 30 characters.", + "TUTORIAL_NOT_SHOW_AGAIN": "Do not show again", + "TUTORIAL_SWIPE_LEFT": "Swipe left to delete corresponding record.", + "TUTORIAL_SWIPE_RIGHT": "Swipe right to bookmark the text of corresponding record / edit the tag of corresponding bookmark.", + "UNDO_DELETE": "You can undo the deletion", + "VIBRATION_EXPLAIN": "Provide vibration or haptic feedback. Please note that not all devices support this feature." }, "BARCODE_TYPE": { - "UPC": "Universal Product Code ", - "EAN": "European Article Number ", "1D": "1D Barcode ", "AZTEC": "Aztec Code ", "DATA_MATRIX": "Data Matrix ", + "EAN": "European Article Number ", "MAXICODE": "MaxiCode ", "PDF_417": "PDF417 ", "QR_CODE": "QR Code ", - "RSS": "GS1 DataBar " + "RSS": "GS1 DataBar ", + "UPC": "Universal Product Code " }, "UPDATE": { - "UPDATE_NOTES_ANDROID": "
  • UI updated
  • Improved performace and fixed known issues
", - "UPDATE_NOTES_IOS": "
  • Fixed issue of showing patch notes again and again
  • Fixed issue of saving contact
" + "UPDATE_NOTES_ANDROID": "

This release brings you several updates and new features. Please check GitHub for details.

", + "UPDATE_NOTES_IOS": "

This release brings you several updates and new features. Please check GitHub for details.

" } } \ No newline at end of file diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json new file mode 100644 index 0000000..8bf4172 --- /dev/null +++ b/src/assets/i18n/fr.json @@ -0,0 +1,322 @@ +{ + "100_RECORDS": "100 enregistrements", + "30_RECORDS": "30 enregistrements", + "49_DIGIT": "49 chiffres", + "50_RECORDS": "50 enregistrements", + "ABOUT": "À propos", + "ABOUT_SIMPLE_QR": "À propos de Simple QR", + "ADD": "Ajouter", + "ADD_BCC": "Ajouter CCI", + "ADD_CC": "Ajouter CC", + "ADD_CONTACT": "Ajouter le contact", + "ADD_RECIPIENT": "Ajouter un destinataire", + "APP": "Application", + "APPEARANCE_AND_EFFECTS": "Apparence et Effets", + "APP_INITIAL_PAGE": "Première page de l'application", + "APP_VERSION": "Version de l'application", + "AT": "à", + "AT_LEAST_1_MINUTE_LATER": "Au moins 1 minute plus tard", + "AT_LEAST_3_MINUTES_LATER": "Au moins 3 minutes plus tard", + "AT_LEAST_5_MINUTES_LATER": "Au moins 5 minutes plus tardr", + "AUTO_KILL_BACKGROUND": "Arrière-plan de mise à mort automatique", + "AUTO_LOGGING": "Journalisation automatique", + "AUTO_MAX_BRIGHTNESS": "Luminosité max automatique", + "AUTO_OPEN_URL": "Ouvrir l'URL automatique", + "AUTO_QR_CODE_POPUP": "Fenêtre pop-up de code QR automatique", + "BACKGROUND_COLOR": "Couleur de l'arrière plan", + "BACKING_UP": "Sauvegarde", + "BACKUP": "Sauvegarde", + "BASE64": "Base64", + "BASE64_DECODED": "Décodé en Base64", + "BASE64_ENCODED": "Encodé en Base64", + "BCC": "CCI", + "BLACK": "Noir", + "BOOKMARK": "Signet", + "BOOKMARKED": "Mis en signet", + "BOOKMARKED_TEXTS": "Textes marqués d'un signet", + "BOOKMARKS": "Signets", + "BRAVE_SEARCH": "Brave Search", + "BROWSE": "Parcourir", + "BROWSER": "Navigateur", + "BROWSE_WEBSITE": "Parcourir le site Web", + "CALL": "Appeler", + "CANCEL": "Annuler", + "CC": "CC", + "CITY": "Ville", + "CLEAR": "Nettoyer", + "CLOSE": "Fermer", + "COLOR": "Couleur", + "COLOR_THEME": "Thème de couleur", + "COMING_SOON": "Bientôt disponible", + "CONTACT_METHOD": "Méthode de contact", + "CONTACT_NAME": "Nom du contact", + "CONTENT": "Le contenu", + "CONTENT_TYPE": "Type de contenu", + "COPIED": "Copliqué", + "COPY": "Copier", + "COPY_SECRET_AND_SAVE_BACKUP": "Copier le secret et enregistrer la sauvegarde", + "COPY_TEXT": "Copier le texte", + "COUNTRY": "Pays", + "CREATE": "Créer", + "CREATED": "Créé", + "CREATE_QR_CODE": "Créer un code QR", + "DARK": "Sombre", + "DATE_OF_BIRTH": "Date de naissance", + "DEBUG_MODE": "Mode débogage", + "DECODING": "Décoder", + "DECRYPTING": "Décrypter", + "DETAILED": "Détaillé", + "DONE": "Fini", + "DUCK_DUCK_GO": "DuckDuckGo", + "ECOSIA": "Ecosia", + "EDIT": "Modifier", + "EMAIL_ADDRESS": "Adresse e-mail", + "EMAIL_BODY": "Corps de l'e-mail", + "EMAIL_NTT_DOCOMO": "E-mail (NTT Docomo)", + "EMAIL_RECIPIENT": "Destinataire de l'e-mail", + "EMAIL_SUBJECT": "Sujet de l'email", + "EMAIL_TO": "Envoyer à", + "EMAIL_W3C_STANDARD": "E-mail (norme W3C)", + "ENCRYPTING": "Crypter", + "ERROR_CORRECTION_LEVEL": "Niveau de correction d'erreur", + "EXIT": "Quitter", + "EXIT_APP": "Quitter l'application", + "EXPORT": "Exporter", + "EXPORTING": "Exportation", + "EXPORT_TO_CSV": "Exporter vers CSV", + "FAX_NUMBER": "Numéro de fax", + "FEMALE": "Femelle", + "FIRST_NAME": "Prénom", + "FOLLOW_SYSTEM_SETTINGS": "Suivre les paramètres du système", + "FREE_TEXT": "Texte libre", + "FULL_RESET": "Réinitialiser complètement", + "FUNCTIONS": "Fonctions", + "GENDER": "Sexe", + "GEOLOCATION": "Géolocalisation", + "GOOGLE_SEARCH": "Recherche Google", + "HAPTIC_FEEDBACK_ONLY": "Retour haptique uniquement", + "HIDDEN_NETWORK_?": "Réseau caché ?", + "HOME_ADDRESS": "Adresse du domicile", + "HOME_PHONE_NUMBER": "Numéro de téléphone résidentiel", + "ICON_ONLY": "Icône uniquement", + "IMPORT": "Importer", + "IMPORT_FROM_CSV": "Importer depuis CSV", + "IMPORT_IMAGE": "Importer une image", + "INITIAL_SEGMENT": "Segment initial", + "JOB_TITLE": "Titre d'emploi", + "LANGUAGE": "Langue", + "LAST_NAME": "Nom de famille", + "LATITUDE": "Latitude", + "LEVEL_H": "Niveau H", + "LEVEL_L": "Niveau L", + "LEVEL_M": "Niveau M", + "LEVEL_Q": "Niveau Q", + "LIGHT": "Clair", + "LOADING_DATA": "Charger des données", + "LOCK_LANDSCAPE": "Verrouiller le paysage", + "LOCK_PORTRAIT": "Verrouiller le portrait", + "LOG": "Registre", + "LOG_BACKUP_AND_RESTORE": "Journalisation, sauvegarde et restauration", + "LONGITUDE": "Longitude", + "MALE": "Mâle", + "MANAGE_RECORDS": "Gérer les enregistrements", + "MARGIN": "Marge", + "MESSAGE": "Message", + "MESSAGE_CONTENT": "Contenu du message", + "MICROSOFT_BING": "Microsoft Bing", + "MOBILE_PHONE_NUMBER": "Numéro de portable", + "MORE": "Plus", + "NAME": "Nom", + "NO": "Non", + "NONE": "Aucun", + "NOT_AVAILABLE": "Pas disponible", + "NOT_PROVIDED": "Non fourni", + "NOT_TO_DISCLOSE": "A ne pas dévoiler", + "NO_LIMIT": "Aucune limite", + "NUMBER_OF_RECORDS": "Nombre d'enregistrements", + "OK": "OK", + "ONLY_DELETE_DATA": "Supprimer uniquement les données", + "ONLY_RESET_SETTING": "Paramètre de réinitialisation uniquement", + "OPEN": "Ouvrir", + "OPEN_URL": "Ouvrir l'URL", + "OPEN_WITH_...": "Ouvrir avec...", + "OPTIMIZING_DATA_...": "Optimiser les données...", + "ORGANIZATION": "Organisme", + "ORIGINAL": "Original", + "OTHERS": "Autres", + "PASSWORD": "Mot de passe", + "PATCH_NOTES": "Notes de mise à jour", + "PERMISSION_REQUIRED": "Autorisation requise", + "PERSONAL": "Personnel", + "PHONE_NO": "Numéro de téléphone", + "PHONE_NUMBER": "Numéro de téléphone", + "PLEASE_WAIT": "Veuillez patienter", + "POSTAL_CODE": "code postal", + "PREPARING": "En train de préparer", + "PREVIEW": "Aperçu", + "PRIVACY_POLICY": "Politique de confidentialité", + "QR_CODE": "Code QR", + "QR_CODE_AND_DECODED_RESULT": "Code QR et résultat décodé", + "QR_CODE_CONTENT": "Contenu du code QR", + "QR_CODE_STYLE": "Style de code QR", + "RATE_THE_APP": "Évaluer l'application", + "RECORDS_LIMIT": "Limite d'enregistrements", + "REMOVE_ALL": "Enlever tout", + "REMOVE_BCC": "Supprimer le CCI", + "REMOVE_CC": "Supprimer le CC", + "REMOVE_RECIPIENT": "Supprimer le destinataire", + "REPORT_ISSUE": "Signaler un problème", + "RESET_APP": "Réinitialiser l'application", + "RESET_DEFAULT": "Réinitialiser par défaut", + "RESTORE": "Restaurer", + "RESULT": "Résultat", + "SAVE": "Enregistrer", + "SAVED": "Enregistrée", + "SCAN": "Scanner", + "SCANNED": "Scanné", + "SCANNING_FEEDBACK_ONLY": "Vibrer lors de la numérisation uniquement", + "SCAN_BY_CAMERA": "Numérisation par caméra", + "SCREEN_ORIENTATION": "Orientation de l'écran", + "SEARCH": "Chercher", + "SEARCH_ENGINE": "Moteur de recherche", + "SECRET": "Code confidentiel", + "SEND": "Envoyer", + "SEND_EMAIL": "Envoyer un e-mail", + "SEND_MESSAGE": "Envoyer le message", + "SETTING": "Paramètre", + "SETTINGS": "Paramètres", + "SHARE": "Partager", + "SHARING": "Partager", + "SHOW": "Afficher", + "SHOW_NUMBER_OR_RECORDS": "Afficher le nombre d'enregistrements", + "SHOW_QR_CODE": "Afficher le code QR", + "SIMPLE_QR": "Simple QR", + "STATE": "Etat", + "STREET": "Rue", + "SUCCESS": "Succès", + "SUPPORTED_BARCODE_TYPE": "Type de code-barres pris en charge", + "SUPPORTED_TYPE": "Type pris en charge", + "SYSTEM_DEFAULT": "Système par défaut", + "TASK": "Tâche", + "TASK_BUTTON_LAYOUT": "Disposition des boutons de tâche", + "TURN_OFF": "Éteindre", + "TURN_ON": "Allumer", + "TURNED_OFF": "Désactivé", + "TURNED_ON": "Activée", + "TUTORIAL": "Didacticiel", + "UNDO": "Annuler", + "UNKNOWN": "Inconnu", + "UPDATE_SUCCESSFULLY": "Mise à jour réussie", + "URL": "URL", + "VCARD_CONTACT": "Contact vCard", + "VERSION_VERSION": "Version {version}", + "VIBRATION": "Vibration", + "VIEWED": "Vu", + "VIEW_BOOKMARK": "Afficher le signet", + "VIEW_GITHUB": "Afficher le GitHub", + "VIEW_LOG": "Afficher le journal", + "VIEW_STORE_AND_SOURCE_CODE": "Afficher le magasin et le code source", + "VIEW_INSTRUCTIONS": "Afficher les instructions", + "WEBSITE": "Site Web", + "WIFI": "Wi-Fi", + "WIFI_ENCRYPTION": "Cryptage Wi-Fi", + "WIFI_SSID": "SSID Wi-Fi", + "WORK": "Travail", + "WORK_PHONE_NUMBER": "Numéro de téléphone professionnel", + "YAHOO_SEARCH": "Yahoo! Recherche", + "YANDEX": "Yandex", + "YES": "Oui", + "MSG": { + "ALREADY_BOOKMARKED": "Manqué! Déjà mis en signet avant", + "AUTO_KILL_BACKGROUND_EXPLAIN": "

Pour économiser les ressources système et la batterie, définissez l'heure pour arrêter automatiquement l'application lorsqu'elle s'exécute en arrière-plan.


Si vous choisissez Suivre les paramètres du système, l'application sera entièrement contrôlée par le système et ne sera pas tuée d'elle-même.


Veuillez noter que dans certaines situations, le système peut arrêter l'application à l'avance.

", + "AUTO_LOGGING_EXPLAIN": "Chaque contenu de code QR et de code-barres est enregistré et stocké automatiquement après que vous l'avez numérisé, créé ou visualisé à nouveau. Vous pouvez les afficher sur la page Registre.", + "AUTO_MAX_BRIGHTNESS_EXPLAIN": "La luminosité de l'écran est automatiquement ajustée au maximum lors de l'affichage du code QR.", + "AUTO_OPEN_URL_EXPLAIN": "Lorsque le code QR est scanné et que le contenu est une URL, l'URL s'ouvre automatiquement.", + "AUTO_SHOW_QR_EXPLAIN": "Afficher automatiquement le code QR sur la page de résultat après les actions suivantes.", + "BACKUP_EXPLAIN": "Vous pouvez sauvegarder tous les enregistrements et signets localement. Vous recevrez un ensemble de code secret après la sauvegarde. Conservez le secret en toute sécurité, sinon vous ne pourrez pas restaurer la sauvegarde. Veuillez noter que Simple QR ne prend pas en charge la sauvegarde et la restauration multiplateforme.", + "BACKUP_FAILED": "Échec de la sauvegarde", + "BACKUP_FAILED_2": "Échec de la sauvegarde. Veuillez vous assurer que l'autorisation de stockage a été accordée.", + "BACKUP_SUCCESSFULLY": "

Sauvegarde réussie. Veuillez enregistrer le fichier de sauvegarde et conserver le secret suivant en toute sécurité

{secret}

", + "BARCODE_TYPE": "Scanner
  • Code QR
  • Code-barres 1D
  • Code aztèque
  • Data Matrix
  • PDF417
Importer une image
  • Code QR
Créer
  • Code QR
", + "BOOKMARKED": "Mis en signet avec succès", + "BUTTON_DISPLAY_EXPLAIN": "Afficher ou masquer les boutons de tâche.", + "BUTTON_STYLE_EXPLAIN": "Choisissez le style des boutons de tâche.", + "CAMERA_PERMISSION": "Pour activer la numérisation, vous devez accorder l'autorisation Caméra.", + "CONTACT_PERMISSION": "Vous devez accorder l'autorisation Contact pour enregistrer le contact.", + "COPIED_SECRET": "Code secret copié", + "COPY_TEXT": "Quel contenu souhaitez-vous copier ?", + "CREATE_QRCODE_MAX_LENGTH": "Max. 1817 caractères", + "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "La longueur du contenu du QR Code généré ne doit pas dépasser 1817 caractères.", + "DEBUG_MODE_ON": "Le mode débogage a bien été activé", + "DELETE_OVERFLOWED_RECORDS": "Après avoir quitté cette page, les enregistrements débordés seront définitivement supprimés.", + "EMAIL_MAX_LENGTH": "Max. 254 caractères", + "EMAIL_SUBJECT_MAX_LENGTH": "Max. 78 caractères", + "ERROR_CORRECTION_LEVEL_EXPLAIN": "

Le code QR a une capacité de correction d'erreur pour restaurer les données même si le code est endommagé.


Il existe 4 niveaux disponibles :

Le niveau L restaure 7 % des octets de données.

Le niveau M restaure 15 % des octets de données.

Le niveau Q restaure 25 % des octets de données.

Le niveau H restaure 30 % des octets de données.


N'oubliez pas que l'augmentation du niveau peut améliorer la capacité de correction des erreurs, mais augmente également la taille du code QR. Par conséquent, pour les cas généraux, le niveau M est recommandé.

", + "EXIT_APP": "

Voulez-vous vraiment quitter l'application ?

Si vous aimez Simple QR, veuillez l'évaluer sur la magasin.

", + "EXPORT_TO_CSV_EXPLAIN": "Vous pouvez exporter tous les enregistrements et signets dans un fichier CSV.", + "FAILED_SAVING_CONTACT": "Échec de l'enregistrement des contacts", + "FAIL_PREPARE_SMS": "Échec de l'envoi du message", + "IMPORT_FAILED": "Échec de l'importation", + "IMPORT_FROM_CSV_EXPLAIN": "Vous pouvez importer des enregistrements et des signets à partir d'un fichier CSV au format défini par Simple QR. Si vous souhaitez transférer des données entre Android et iOS, veuillez utiliser cette fonction.", + "INPUT_TAG": "Veuillez donner une étiquette au signet", + "INVALID_BK_FILE": "Ce fichier de sauvegarde n'est pas valide.", + "INVALID_CSV_FILE": "Ce fichier CSV n'est pas valide.", + "NOT_BASE64_DE": "Les données ne peuvent pas être décodées en Base64", + "NOT_BASE64_EN": "Les données ne peuvent pas être encodées en Base64", + "NOT_BASE64_EN_DE": "Les données ne peuvent pas être encodées et décodées en Base64", + "NO_QR_CODE": "Ne peut pas détecter le code QR", + "ONLY_VCARD_3_0": "Seul vCard 3.0 est pris en charge", + "PLEASE_INPUT_VALID_SECRET": "Veuillez entrer un code valide", + "PORTRAIT_ONLY": "Portrait verrouillé", + "PREPARE_SMS": "Préparer le message", + "PREVIOUS_RECORDS": "Enregistrements précédents issus de la numérisation, de la création ou de l'affichage", + "QR_CODE_VALUE_NOT_EMPTY": "La valeur du code QR ne peut pas être vide", + "READ_IMAGE_PERMISSION": "Vous devez accorder l'autorisation de stockage pour numériser l'image.", + "RECORDS_LIMIT_EXPLAIN": "Limitez le nombre d'enregistrements pouvant être stockés. L'enregistrement le plus ancien sera supprimé lorsque la limite sera atteinte.", + "REMOVE_ALL_BOOKMARKS": "Êtes-vous sûr de supprimer tous les textes marqués d'un signet ? Cette action ne peut pas être annulée.", + "REMOVE_ALL_RECORD": "Êtes-vous sûr de supprimer tous les enregistrements ? Cette action ne peut pas être annulée.", + "RESET_APP": "Êtes-vous sûr de réinitialiser cette application et de supprimer toutes les données ? Cette action ne peut pas être annulée.", + "RESET_DEFAULT": "Êtes-vous sûr de réinitialiser par défaut ?", + "RESTORE_EXPLAIN": "Vous pouvez restaurer une sauvegarde précédente des enregistrements et des signets. Les éléments de la sauvegarde seront fusionnés avec les enregistrements existants. Le type de fichier de sauvegarde doit être TFSQBK.", + "RESTORE_EXPLAIN_IOS": "Vous pouvez restaurer une sauvegarde précédente des enregistrements et des signets. Les éléments de la sauvegarde seront fusionnés avec les enregistrements existants. Le type de fichier de sauvegarde doit être ISQBK.", + "RESTORE_FAILED": "Échec de la restauration", + "RESTORE_SECRET": "Veuillez entrer le code secret pour restaurer les enregistrements", + "RESTORE_SUCCESSFUL": "Restauré avec succès", + "RESTORE_WRONG_SECRET": "Code incorrect", + "SAVED_CONTACT": "Enregistré le contact", + "SAVING_CONTACT": "Enregistre le contact", + "SCAN_QR_FROM_IMAGE": "Simple QR utilise le module \"cozmo/jsQR\" pour scanner le code QR à partir de l'image. Pour numériser avec succès,", + "SCAN_QR_FROM_IMAGE_R1": "Veuillez autoriser l'application à accéder au stockage ou à la photothèque.", + "SCAN_QR_FROM_IMAGE_R2": "Veuillez vous assurer que l'arrière-plan de l'image sélectionnée n'est pas transparent. Il est recommandé de choisir une image qui n'est pas capturée par l'appareil photo.", + "SCAN_QR_FROM_IMAGE_R3": "Veuillez annuler la retouche photo si vous souhaitez numériser l'image complète. (le cas échéant)", + "SEARCH": "Quel contenu souhaitez-vous rechercher ?", + "SEARCH_ENGINE_EXPLAIN": "Choisissez le moteur de recherche pour effectuer une recherche sur le Web après avoir obtenu le résultat.", + "SHARE_QR": "Il suffit de le scanner !\n\nPartagé par Simple QR", + "SMS_MAX_LENGTH": "Max. 160 caractères", + "SSID_MAX_LENGTH": "Max. 32 caractères", + "START_PAGE_EXPLAIN": "Choisissez la page initiale de l'application.", + "START_PAGE_HEADER_EXPLAIN": "Afficher l'en-tête Simple QR sur la page initiale.", + "TAG_MAX_LENGTH": "Max. 30 caractères", + "TAG_MAX_LENGTH_EXPLAIN": "La longueur de la balise ne doit pas dépasser 30 caractères.", + "TUTORIAL_NOT_SHOW_AGAIN": "Ne pas montrer de nouveau", + "TUTORIAL_SWIPE_LEFT": "Balayez vers la gauche pour supprimer l'enregistrement correspondant.", + "TUTORIAL_SWIPE_RIGHT": "Balayez vers la droite pour mettre en signet le texte de l'enregistrement / modifier la balise du signet correspondant.", + "UNDO_DELETE": "Vous pouvez annuler la suppression", + "VIBRATION_EXPLAIN": "Fournir des vibrations ou un retour haptique. Veuillez noter que tous les appareils ne prennent pas en charge cette fonctionnalité." + }, + "BARCODE_TYPE": { + "1D": "Code-barres 1D", + "AZTEC": "Code aztèque", + "DATA_MATRIX": "Matrice de données", + "EAN": "Numéro d'article européen", + "MAXICODE": "MaxiCode", + "PDF_417": "PDF417 ", + "QR_CODE": "Code QR", + "RSS": "Barre de données GS1", + "UPC": "Code produit universel" + }, + "UPDATE": { + "UPDATE_NOTES_ANDROID": "

Cette version vous apporte plusieurs mises à jour et nouvelles fonctionnalités. Veuillez consulter GitHub pour plus de détails.

", + "UPDATE_NOTES_IOS": "

Cette version vous apporte plusieurs mises à jour et nouvelles fonctionnalités. Veuillez consulter GitHub pour plus de détails.

" + } +} \ No newline at end of file diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json new file mode 100644 index 0000000..575e0d1 --- /dev/null +++ b/src/assets/i18n/it.json @@ -0,0 +1,322 @@ +{ + "100_RECORDS": "100 record", + "30_RECORDS": "30 record", + "49_DIGIT": "49-digit", + "50_RECORDS": "50 record", + "ABOUT": "Informazioni", + "ABOUT_SIMPLE_QR": "Informazioni su Simple QR", + "ADD": "Aggiungi", + "ADD_BCC": "Aggiungi BCC", + "ADD_CC": "Aggiungi CC", + "ADD_CONTACT": "Aggiungi Contatto", + "ADD_RECIPIENT": "Aggiungi Destinatario", + "APP": "App", + "APPEARANCE_AND_EFFECTS": "Aspetto e Effetti", + "APP_INITIAL_PAGE": "Pagina iniziale", + "APP_VERSION": "Versione", + "AT": "alle", + "AT_LEAST_1_MINUTE_LATER": "Almeno 1 minuto dopo", + "AT_LEAST_3_MINUTES_LATER": "Almeno 3 minuti dopo", + "AT_LEAST_5_MINUTES_LATER": "Almeno 5 minuti dopo", + "AUTO_KILL_BACKGROUND": "Eliminazione background automatica", + "AUTO_LOGGING": "Logging automatico", + "AUTO_MAX_BRIGHTNESS": "Luminosità massima automatica", + "AUTO_OPEN_URL": "Apri URL automaticamente", + "AUTO_QR_CODE_POPUP": "Codice QR Pop-up automatico", + "BACKGROUND_COLOR": "Colore di sfondo", + "BACKING_UP": "Eseguendo il backup", + "BACKUP": "Backup", + "BASE64": "Base64", + "BASE64_DECODED": "Base64 Decoded", + "BASE64_ENCODED": "Base64 Encoded", + "BCC": "BCC", + "BLACK": "Black", + "BOOKMARK": "Segnalibro", + "BOOKMARKED": "Aggiunto ai preferiti", + "BOOKMARKED_TEXTS": "Testi preferiti", + "BOOKMARKS": "Segnalibri", + "BRAVE_SEARCH": "Brave Search", + "BROWSE": "Naviga", + "BROWSER": "Browser", + "BROWSE_WEBSITE": "Sfoglia il sito web", + "CALL": "Chiama", + "CANCEL": "Cancella", + "CC": "CC", + "CITY": "Città", + "CLEAR": "Pulisci", + "CLOSE": "Chiudi", + "COLOR": "Colore", + "COLOR_THEME": "Tema colore", + "COMING_SOON": "Prossimamente", + "CONTACT_METHOD": "Metodi di contatto", + "CONTACT_NAME": "Nome", + "CONTENT": "Contenuto", + "CONTENT_TYPE": "Tipo di contenuto", + "COPIED": "Copiato", + "COPY": "Copia", + "COPY_SECRET_AND_SAVE_BACKUP": "Copia dato segreto e Salva un backup", + "COPY_TEXT": "Copia testo", + "COUNTRY": "Paese", + "CREATE": "Crea", + "CREATED": "Creato", + "CREATE_QR_CODE": "Crea Codice QR", + "DARK": "Scuro", + "DATE_OF_BIRTH": "Data di nascita", + "DEBUG_MODE": "Modalità di debug", + "DECODING": "Decodifica", + "DECRYPTING": "Decifra", + "DETAILED": "Dettagli", + "DONE": "Completato", + "DUCK_DUCK_GO": "DuckDuckGo", + "ECOSIA": "Ecosia", + "EDIT": "Modifica", + "EMAIL_ADDRESS": "Email", + "EMAIL_BODY": "Corpo della mail", + "EMAIL_NTT_DOCOMO": "Email (NTT Docomo)", + "EMAIL_RECIPIENT": "Destinatario email", + "EMAIL_SUBJECT": "Soggetto email", + "EMAIL_TO": "Invia una mail a", + "EMAIL_W3C_STANDARD": "Email (W3C Standard)", + "ENCRYPTING": "Crittografa", + "ERROR_CORRECTION_LEVEL": "Errore di correttezza", + "EXIT": "Esci", + "EXIT_APP": "Esci dall'app", + "EXPORT": "Esporta", + "EXPORTING": "Esportazione in corso", + "EXPORT_TO_CSV": "Esporta in CSV", + "FAX_NUMBER": "Numero fax", + "FEMALE": "Donna", + "FIRST_NAME": "Nome", + "FOLLOW_SYSTEM_SETTINGS": "Segui le impostazioni di sistema", + "FREE_TEXT": "Testo", + "FULL_RESET": "Reset", + "FUNCTIONS": "Opzioni", + "GENDER": "Sesso", + "GEOLOCATION": "Geolocalizzazione", + "GOOGLE_SEARCH": "Ricerca con Google", + "HAPTIC_FEEDBACK_ONLY": "Feedback disponibile solo tramite Haptic", + "HIDDEN_NETWORK_?": "Rete nascosta?", + "HOME_ADDRESS": "Indirizzo di casa", + "HOME_PHONE_NUMBER": "Numero di telefono di casa", + "ICON_ONLY": "Disponibile solo icona", + "IMPORT": "Importa", + "IMPORT_FROM_CSV": "Importa da CSV", + "IMPORT_IMAGE": "Importa immagine", + "INITIAL_SEGMENT": "Segmento iniziale", + "JOB_TITLE": "Titolo di lavoro", + "LANGUAGE": "Lingua", + "LAST_NAME": "Cognome", + "LATITUDE": "Latitudine", + "LEVEL_H": "Livello H", + "LEVEL_L": "Livello L", + "LEVEL_M": "Livello M", + "LEVEL_Q": "Livello Q", + "LIGHT": "Chiaro", + "LOADING_DATA": "Caricamento dati", + "LOCK_LANDSCAPE": "Blocca vista orizzontale", + "LOCK_PORTRAIT": "Blocca vista verticale", + "LOG": "Log", + "LOG_BACKUP_AND_RESTORE": "Logga, Esegui backup & Ripristina", + "LONGITUDE": "Longitudine", + "MALE": "Uomo", + "MANAGE_RECORDS": "Gestisci record", + "MARGIN": "Margine", + "MESSAGE": "Messaggio", + "MESSAGE_CONTENT": "Contenuto del messaggio", + "MICROSOFT_BING": "Microsoft Bing", + "MOBILE_PHONE_NUMBER": "Numero di telefono cellulare", + "MORE": "Più", + "NAME": "Nome", + "NO": "No", + "NONE": "Nessuno", + "NOT_AVAILABLE": "Non disponibile", + "NOT_PROVIDED": "Non fornito", + "NOT_TO_DISCLOSE": "Non divulgare", + "NO_LIMIT": "Senza limiti", + "NUMBER_OF_RECORDS": "Numero di record", + "OK": "OK", + "ONLY_DELETE_DATA": "Elimina esclusivamente i dati", + "ONLY_RESET_SETTING": "Reset delle impostazioni", + "OPEN": "Aprire", + "OPEN_URL": "Aprire l'URL", + "OPEN_WITH_...": "Apri con...", + "OPTIMIZING_DATA_...": "Ottimizzazione dei dati...", + "ORGANIZATION": "Organizzazione", + "ORIGINAL": "Orginale", + "OTHERS": "Altri", + "PASSWORD": "Password", + "PATCH_NOTES": "Note di Patch", + "PERMISSION_REQUIRED": "Permesso Richiesto", + "PERSONAL": "Personale", + "PHONE_NO": "Numero di telefono", + "PHONE_NUMBER": "Numero di telefono", + "PLEASE_WAIT": "Attendere", + "POSTAL_CODE": "Codice postale", + "PREPARING": "In preparazione", + "PREVIEW": "Anteprima", + "PRIVACY_POLICY": "Politica sulla riservatezza", + "QR_CODE": "Codice QR", + "QR_CODE_AND_DECODED_RESULT": "Codice QR e risultato decodificato", + "QR_CODE_CONTENT": "Contenuto QR code", + "QR_CODE_STYLE": "Stile codice QR", + "RATE_THE_APP": "Valuta App", + "RECORDS_LIMIT": "Limite di record", + "REMOVE_ALL": "Elimina tutti", + "REMOVE_BCC": "Elimina BCC", + "REMOVE_CC": "Elimina CC", + "REMOVE_RECIPIENT": "Elimina destinatario", + "REPORT_ISSUE": "Segnala un problema", + "RESET_APP": "Reset App", + "RESET_DEFAULT": "Ripristina predefinito", + "RESTORE": "Ripristina", + "RESULT": "Risultato", + "SAVE": "Salva", + "SAVED": "Salvato", + "SCAN": "Scannerizza", + "SCANNED": "Scannerizzato", + "SCANNING_FEEDBACK_ONLY": "Scansione esclusivamente del feedback", + "SCAN_BY_CAMERA": "Scansione utilizzando la fotocamera", + "SCREEN_ORIENTATION": "Orientamento schermo", + "SEARCH": "Ricerca", + "SEARCH_ENGINE": "Motore di ricerca", + "SECRET": "Segreto", + "SEND": "Invia", + "SEND_EMAIL": "Invia Email", + "SEND_MESSAGE": "Invia Messaggio", + "SETTING": "Impostazione", + "SETTINGS": "Impostazioni", + "SHARE": "Condividi", + "SHARING": "Condividendo", + "SHOW": "Mostra", + "SHOW_NUMBER_OR_RECORDS": "Mostra il numero di record", + "SHOW_QR_CODE": "Mostra il Codice QR", + "SIMPLE_QR": "Simple QR", + "STATE": "Stato", + "STREET": "Strada", + "SUCCESS": "Successo", + "SUPPORTED_BARCODE_TYPE": "Tipi di codice a barra supportati", + "SUPPORTED_TYPE": "Tipi supportati", + "SYSTEM_DEFAULT": "Sistema Predefinito", + "TASK": "Operazione", + "TASK_BUTTON_LAYOUT": "Disposizione Pulsante Operazione", + "TURN_OFF": "Spegni", + "TURN_ON": "Accendi", + "TURNED_OFF": "Spento", + "TURNED_ON": "Acceso", + "TUTORIAL": "Guida", + "UNDO": "Annulla", + "UNKNOWN": "Sconosciuto", + "UPDATE_SUCCESSFULLY": "Aggiornamento completato con successo", + "URL": "URL", + "VCARD_CONTACT": "Contatto vCard", + "VERSION_VERSION": "Versione {version}", + "VIBRATION": "Vibrazione", + "VIEWED": "Visualizzati", + "VIEW_BOOKMARK": "Visualizza segnalibri", + "VIEW_GITHUB": "Visualizza GitHub", + "VIEW_LOG": "Visualizza Log", + "VIEW_STORE_AND_SOURCE_CODE": "Visualizza Store & Codice Sorgente", + "VIEW_INSTRUCTIONS": "Visualizza le istruzioni", + "WEBSITE": "Sito Web", + "WIFI": "WiFi", + "WIFI_ENCRYPTION": "Crittografia WiFi", + "WIFI_SSID": "WiFi SSID", + "WORK": "Lavoro", + "WORK_PHONE_NUMBER": "Numero di telefono di lavoro", + "YAHOO_SEARCH": "Yahoo! Search", + "YANDEX": "Yandex", + "YES": "SI", + "MSG": { + "ALREADY_BOOKMARKED": "Errore! Segnalibro già salvato", + "AUTO_KILL_BACKGROUND_EXPLAIN": "

Per salvare risorse e batteria, setta automaticamente la durata massima per killare l'app automaticamente quando viene eseguita in background.


Se selezioni Segui le impostazioni di sistema, l'app verrà controllata dal sistema e non sarà possibile killarla in autonomia.


Considera che in alcune situazioni, il sistema potrebbe interrompere l'app in anticipo..

", + "AUTO_LOGGING_EXPLAIN": "Qualsiasi contenuto dei codici QR e a barre vengono loggate e salvate automaticamente in seguito alla scannerizzazione, creazione o successiva visualizzazione. Puoi rivederla nella pagina di Log.", + "AUTO_MAX_BRIGHTNESS_EXPLAIN": "La luminosità dello schermo è settata automaticamente al massimo quando si visualizza un codice QR.", + "AUTO_OPEN_URL_EXPLAIN": "Quando il codice QR viene scansionato e il contenuto è un URL, l'URL verrà aperto automaticamente.", + "AUTO_SHOW_QR_EXPLAIN": "Visualizza automaticamente il codice QR nella pagina dei risultati dopo le seguenti azioni.", + "BACKUP_EXPLAIN": "È possibile eseguire il backup di tutti i record e i segnalibri localmente. Ti verrà fornita una serie di chiavi segrete dopo il backup. Salvale in modo sicuro, altrimenti non sarà possibile ripristinare il backup. Si prega di notare che Simple QR non supporta il backup e il ripristino multipiattaforma.", + "BACKUP_FAILED": "Tentativo di backup fallito", + "BACKUP_FAILED_2": "Tentativo di backup fallito. Assicurati che l'autorizzazione di archiviazione sia stata concessa.", + "BACKUP_SUCCESSFULLY": "

Backup riuscito. Si prega di salvare il file di backup e salvare la seguente chiave segreta in modo sicuro

{secret}

", + "BARCODE_TYPE": "Scannerizzazione
  • Codice QR
  • Codice a barre 1D
  • Codice Aztec
  • Matrice di Dati
  • PDF417
Importa Immagine
  • Codice QR
Crea
  • Codice QR
", + "BOOKMARKED": "Segnalibro salvato con successo", + "BUTTON_DISPLAY_EXPLAIN": "Mostra o nascondi il pulsante operazione.", + "BUTTON_STYLE_EXPLAIN": "Scegli lo stile del pulsante operazione.", + "CAMERA_PERMISSION": "Per abilitare la scansione, è necessario concedere l'autorizzazione Fotocamera.", + "CONTACT_PERMISSION": "È necessario concedere l'autorizzazione Contatto per il salvataggio del contatto.", + "COPIED_SECRET": "Chiave segreta copiata", + "COPY_TEXT": "Quale contenuto vuoi copiare?", + "CREATE_QRCODE_MAX_LENGTH": "Massimo di 1817 caratteri", + "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "La lunghezza del contenuto del codice QR generato non deve superare i 1817 caratteri.", + "DEBUG_MODE_ON": "Modalità di debug abilitata con successo", + "DELETE_OVERFLOWED_RECORDS": "Dopo aver lasciato questa pagina, i record in overflow verranno eliminati definitivamente.", + "EMAIL_MAX_LENGTH": "Massimo 254 caratteri", + "EMAIL_SUBJECT_MAX_LENGTH": "Massimo 78 caratteri", + "ERROR_CORRECTION_LEVEL_EXPLAIN": "

Il codice QR ha la capacità di correzione degli errori per ripristinare i dati anche se il codice è danneggiato.


Sono disponibili 4 livelli::

Il Livello L recupera il 7% di byte di dati.

Il Livello M recupera il 15% dei byte di dati.

Il Livello Q recupera il 25% di byte di dati.

Il Livello H recupera il 30% di byte di dati.


Si ricorda che l'innalzamento del livello può migliorare la capacità di correzione degli errori, ma aumenta anche la dimensione del codice QR. Pertanto, per i casi generali, si raccomanda il livello M.

", + "EXIT_APP": "

Sei sicuro di voler uscire?

Se ti piace Simple QR, valutala sullo store

", + "EXPORT_TO_CSV_EXPLAIN": "Puoi esportare tutti i record e i segnalibri in un file CSV.", + "FAILED_SAVING_CONTACT": "Salvataggio dei dati fallito", + "FAIL_PREPARE_SMS": "Impossibile inviare il messaggio", + "IMPORT_FAILED": "Impossibile importare", + "IMPORT_FROM_CSV_EXPLAIN": "Puoi importare record e segnalibri da un file CSV con il formato definito da Simple QR. Se desideri trasferire dati tra Android e iOS, utilizza questa funzione.", + "INPUT_TAG": "Si prega di dare un tag al segnalibro", + "INVALID_BK_FILE": "Backup non valido.", + "INVALID_CSV_FILE": "CSV non valido.", + "NOT_BASE64_DE": "I dati non possono essere decodificati in Base64", + "NOT_BASE64_EN": "I dati non possono essere decodificati in Base64", + "NOT_BASE64_EN_DE": "I dati non possono essere decodificati in Base64", + "NO_QR_CODE": "Impossibile leggere il Codice QR", + "ONLY_VCARD_3_0": "Soltanto le vCard 3.0 sono supportate", + "PLEASE_INPUT_VALID_SECRET": "Inserisci un codice valido", + "PORTRAIT_ONLY": "Modalità verticale bloccata", + "PREPARE_SMS": "Preparazione messaggio", + "PREVIOUS_RECORDS": "Record precedenti dalla scansione, creazione o visualizzazione", + "QR_CODE_VALUE_NOT_EMPTY": "Il valore del Codice QR non può essere vuoto", + "READ_IMAGE_PERMISSION": "È necessario concedere l'autorizzazione archiviazione per la scansione dell'immagine.", + "RECORDS_LIMIT_EXPLAIN": "Limita il numero di record che possono essere salvati. Il record più vecchio verrà eliminato al raggiungimento del limite.", + "REMOVE_ALL_BOOKMARKS": "Sei sicuro di voler eliminare tutti i segnalibri testuali? Questa operazione è irreversibile.", + "REMOVE_ALL_RECORD": "Sei sicuro di voler rimuovere tutti i record? Questa operazione è irriversibile.", + "RESET_APP": "Are you sure to reset this app and delete all data? Questa operazione è irriversibile.", + "RESET_DEFAULT": "Sei sicuro di ripristinare il valore predefinito?", + "RESTORE_EXPLAIN": "È possibile ripristinare un backup precedente di record e segnalibri. Gli elementi del backup verranno uniti ai record esistenti. Il tipo di file di backup deve essere TFSQBK.", + "RESTORE_EXPLAIN_IOS": "È possibile ripristinare un backup precedente di record e segnalibri. Gli elementi del backup verranno uniti ai record esistenti. Il tipo di file di backup deve essere ISQBK.", + "RESTORE_FAILED": "Impossibile ripristinare", + "RESTORE_SECRET": "Inserisci la chiave segrete per ripristinare i record", + "RESTORE_SUCCESSFUL": "Ripristinato con successo", + "RESTORE_WRONG_SECRET": "Codice non corretto", + "SAVED_CONTACT": "Contatto salvato", + "SAVING_CONTACT": "Salvataggio contatto", + "SCAN_QR_FROM_IMAGE": "Simple QR utilizza il modulo \"cozmo/jsQR\" per scansionare il codice QR da una immagine. Per eseguire correttamente la scansione,", + "SCAN_QR_FROM_IMAGE_R1": "Autorizza l'app ad accedere a Archiviazione o Libreria foto.", + "SCAN_QR_FROM_IMAGE_R2": "Assicurarsi che lo sfondo dell'immagine selezionata non sia trasparente. Si consiglia di scegliere un'immagine che non viene acquisita dalla fotocamera.", + "SCAN_QR_FROM_IMAGE_R3": "Si prega di annullare il fotoritocco se si desidera eseguire la scansione dell'immagine completa. (se applicabile)", + "SEARCH": "Quali contenuti vuoi cercare?", + "SEARCH_ENGINE_EXPLAIN": "Scegli il motore di ricerca per fare ricerche sul web dopo aver ottenuto il risultato.", + "SHARE_QR": "Basta scansionarlo!\n\nCondiviso da Simple QR", + "SMS_MAX_LENGTH": "Massimo 160 caratteri", + "SSID_MAX_LENGTH": "Massimo 32 caratteri", + "START_PAGE_EXPLAIN": "Seleziona la pagina iniziale dell'app.", + "START_PAGE_HEADER_EXPLAIN": "Mostra l'intestazione Simple QR nella pagina iniziale.", + "TAG_MAX_LENGTH": "Massimo 30 caratteri", + "TAG_MAX_LENGTH_EXPLAIN": "La lunghezza del tag non deve superare i 30 caratteri.", + "TUTORIAL_NOT_SHOW_AGAIN": "Non mostrare di nuovo", + "TUTORIAL_SWIPE_LEFT": "Scorri verso sinistra per eliminare il record corrispondente.", + "TUTORIAL_SWIPE_RIGHT": "Scorri verso destra per aggiungere ai segnalibri il testo del record / modificare il tag del segnalibro corrispondente.", + "UNDO_DELETE": "È possibile annullare l'eliminazione", + "VIBRATION_EXPLAIN": "Fornire vibrazioni o feedback tattile. Tieni presente che non tutti i dispositivi supportano questa funzione." + }, + "BARCODE_TYPE": { + "1D": "Codice a barre 1D ", + "AZTEC": "Codice Aztec ", + "DATA_MATRIX": "Matrice di Dati ", + "EAN": "Numero di articolo europeo ", + "MAXICODE": "MaxiCode ", + "PDF_417": "PDF417 ", + "QR_CODE": "Codice QR ", + "RSS": "Barra Dati GS1 ", + "UPC": "Codice Prodotto Universale " + }, + "UPDATE": { + "UPDATE_NOTES_ANDROID": "

Questa versione offre numerosi aggiornamenti e nuove funzionalità. Si prega di controllare GitHub per i dettagli.

", + "UPDATE_NOTES_IOS": "

Questa versione offre numerosi aggiornamenti e nuove funzionalità. Si prega di controllare GitHub per i dettagli.

" + } +} \ No newline at end of file diff --git a/src/assets/i18n/pt-BR.json b/src/assets/i18n/pt-BR.json new file mode 100644 index 0000000..c3274f0 --- /dev/null +++ b/src/assets/i18n/pt-BR.json @@ -0,0 +1,322 @@ +{ + "100_RECORDS": "100 Registros", + "30_RECORDS": "30 Registros", + "49_DIGIT": "49 dígitos", + "50_RECORDS": "50 Registros", + "ABOUT": "Sobre", + "ABOUT_SIMPLE_QR": "Sobre o Simple QR", + "ADD": "Adicionar", + "ADD_BCC": "Adicionar CCO", + "ADD_CC": "Adicionar CC", + "ADD_CONTACT": "Adicionar Contato", + "ADD_RECIPIENT": "Adicionar Destinatário", + "APP": "Aplicativo", + "APPEARANCE_AND_EFFECTS": "Aparência e Efeitos", + "APP_INITIAL_PAGE": "Página Inicial do Aplicativo", + "APP_VERSION": "Versão do Aplicativo", + "AT": "em", + "AT_LEAST_1_MINUTE_LATER": "Pelo Menos 1 Minuto Depois", + "AT_LEAST_3_MINUTES_LATER": "Pelo Menos 3 Minutos Depois", + "AT_LEAST_5_MINUTES_LATER": "Pelo Menos 5 Minutos Depois", + "AUTO_KILL_BACKGROUND": "Encerrar Automaticamente em Segundo Plano", + "AUTO_LOGGING": "Registro Automático", + "AUTO_MAX_BRIGHTNESS": "Brilho Máximo Automático", + "AUTO_OPEN_URL": "Abrir URL Automaticamente", + "AUTO_QR_CODE_POPUP": "Popup de Código QR Automático", + "BACKGROUND_COLOR": "Cor de Fundo", + "BACKING_UP": "Fazendo Backup", + "BACKUP": "Backup", + "BASE64": "Base64", + "BASE64_DECODED": "Base64 Decodificado", + "BASE64_ENCODED": "Base64 Codificado", + "BCC": "CCO", + "BLACK": "Preto", + "BOOKMARK": "Favoritar", + "BOOKMARKED": "Favoritado", + "BOOKMARKED_TEXTS": "Textos Favoritados", + "BOOKMARKS": "Favoritos", + "BRAVE_SEARCH": "Busca Brave", + "BROWSE": "Pesquisar", + "BROWSER": "Navegador", + "BROWSE_WEBSITE": "Navegar no Site", + "CALL": "Chamada", + "CANCEL": "Cancelar", + "CC": "CC", + "CITY": "Cidade", + "CLEAR": "Limpar", + "CLOSE": "Fechar", + "COLOR": "Cor", + "COLOR_THEME": "Tema de Cores", + "COMING_SOON": "Em Breve", + "CONTACT_METHOD": "Método de Contato", + "CONTACT_NAME": "Nome do Contato", + "CONTENT": "Conteúdo", + "CONTENT_TYPE": "Tipo de Conteúdo", + "COPIED": "Copiado", + "COPY": "Copiar", + "COPY_SECRET_AND_SAVE_BACKUP": "Copiar Segredo e Salvar Backup", + "COPY_TEXT": "Copiar Texto", + "COUNTRY": "País", + "CREATE": "Criar", + "CREATED": "Criado", + "CREATE_QR_CODE": "Criar Código QR", + "DARK": "Escuro", + "DATE_OF_BIRTH": "Data de Nascimento", + "DEBUG_MODE": "Modo de Depuração", + "DECODING": "Decodificando", + "DECRYPTING": "Descriptografando", + "DETAILED": "Detalhado", + "DONE": "Concluído", + "DUCK_DUCK_GO": "DuckDuckGo", + "ECOSIA": "Ecosia", + "EDIT": "Editar", + "EMAIL_ADDRESS": "Endereço de E-mail", + "EMAIL_BODY": "Corpo do E-mail", + "EMAIL_NTT_DOCOMO": "E-mail (NTT Docomo)", + "EMAIL_RECIPIENT": "Destinatário do E-mail", + "EMAIL_SUBJECT": "Assunto do E-mail", + "EMAIL_TO": "Enviar E-mail para", + "EMAIL_W3C_STANDARD": "E-mail (Padrão W3C)", + "ENCRYPTING": "Criptografando", + "ERROR_CORRECTION_LEVEL": "Nível de Correção de Erro", + "EXIT": "Sair", + "EXIT_APP": "Sair do Aplicativo", + "EXPORT": "Exportar", + "EXPORTING": "Exportando", + "EXPORT_TO_CSV": "Exportar para CSV", + "FAX_NUMBER": "Número de Fax", + "FEMALE": "Feminino", + "FIRST_NAME": "Nome Próprio", + "FOLLOW_SYSTEM_SETTINGS": "Seguir Configurações do Sistema", + "FREE_TEXT": "Texto Livre", + "FULL_RESET": "Redefinição Completa", + "FUNCTIONS": "Funções", + "GENDER": "Gênero", + "GEOLOCATION": "Geolocalização", + "GOOGLE_SEARCH": "Busca Google", + "HAPTIC_FEEDBACK_ONLY": "Apenas Feedback Háptico", + "HIDDEN_NETWORK_?": "Rede Oculta?", + "HOME_ADDRESS": "Endereço Residencial", + "HOME_PHONE_NUMBER": "Número de Telefone Residencial", + "ICON_ONLY": "Apenas Ícone", + "IMPORT": "Importar", + "IMPORT_FROM_CSV": "Importar do CSV", + "IMPORT_IMAGE": "Importar Imagem", + "INITIAL_SEGMENT": "Segmento Inicial", + "JOB_TITLE": "Cargo", + "LANGUAGE": "Idioma", + "LAST_NAME": "Sobrenome", + "LATITUDE": "Latitude", + "LEVEL_H": "Nível H", + "LEVEL_L": "Nível L", + "LEVEL_M": "Nível M", + "LEVEL_Q": "Nível Q", + "LIGHT": "Claro", + "LOADING_DATA": "Carregando Dados", + "LOCK_LANDSCAPE": "Travar em Paisagem", + "LOCK_PORTRAIT": "Travar em Retrato", + "LOG": "Registro", + "LOG_BACKUP_AND_RESTORE": "Registro, Backup e Restauração", + "LONGITUDE": "Longitude", + "MALE": "Masculino", + "MANAGE_RECORDS": "Gerenciar Registros", + "MARGIN": "Margem", + "MESSAGE": "Mensagem", + "MESSAGE_CONTENT": "Conteúdo da Mensagem", + "MICROSOFT_BING": "Microsoft Bing", + "MOBILE_PHONE_NUMBER": "Número de Celular", + "MORE": "Mais", + "NAME": "Nome", + "NO": "Não", + "NONE": "Nenhum", + "NOT_AVAILABLE": "Não Disponível", + "NOT_PROVIDED": "Não Fornecido", + "NOT_TO_DISCLOSE": "Não Divulgar", + "NO_LIMIT": "Sem Limite", + "NUMBER_OF_RECORDS": "Número de Registros", + "OK": "OK", + "ONLY_DELETE_DATA": "Apenas Excluir Dados", + "ONLY_RESET_SETTING": "Apenas Restaurar Configuração", + "OPEN": "Abrir", + "OPEN_URL": "Abrir URL", + "OPEN_WITH_...": "Abrir com...", + "OPTIMIZING_DATA_...": "Otimizando Dados...", + "ORGANIZATION": "Organização", + "ORIGINAL": "Original", + "OTHERS": "Outros", + "PASSWORD": "Senha", + "PATCH_NOTES": "Notas de Atualização", + "PERMISSION_REQUIRED": "Permissão Necessária", + "PERSONAL": "Pessoal", + "PHONE_NO": "Nº de Telefone", + "PHONE_NUMBER": "Número de Telefone", + "PLEASE_WAIT": "Aguarde, por favor", + "POSTAL_CODE": "Código Postal", + "PREPARING": "Preparando", + "PREVIEW": "Pré-visualização", + "PRIVACY_POLICY": "Política de Privacidade", + "QR_CODE": "Código QR", + "QR_CODE_AND_DECODED_RESULT": "Código QR e Resultado Decodificado", + "QR_CODE_CONTENT": "Conteúdo do Código QR", + "QR_CODE_STYLE": "Estilo do Código QR", + "RATE_THE_APP": "Avalie o Aplicativo", + "RECORDS_LIMIT": "Limite de Registros", + "REMOVE_ALL": "Remover Todos", + "REMOVE_BCC": "Remover CCO", + "REMOVE_CC": "Remover CC", + "REMOVE_RECIPIENT": "Remover Destinatário", + "REPORT_ISSUE": "Reportar Problema", + "RESET_APP": "Redefinir o Aplicativo", + "RESET_DEFAULT": "Redefinir para o Padrão", + "RESTORE": "Restaurar", + "RESULT": "Resultado", + "SAVE": "Salvar", + "SAVED": "Salvo", + "SCAN": "Escanear", + "SCANNED": "Escaneado", + "SCANNING_FEEDBACK_ONLY": "Apenas Feedback de Escaneamento", + "SCAN_BY_CAMERA": "Escanear pela Câmera", + "SCREEN_ORIENTATION": "Orientação da Tela", + "SEARCH": "Pesquisa", + "SEARCH_ENGINE": "Mecanismo de Busca", + "SECRET": "Segredo", + "SEND": "Enviar", + "SEND_EMAIL": "Enviar E-mail", + "SEND_MESSAGE": "Enviar Mensagem", + "SETTING": "Configuração", + "SETTINGS": "Configurações", + "SHARE": "Compartilhar", + "SHARING": "Compartilhando", + "SHOW": "Mostrar", + "SHOW_NUMBER_OR_RECORDS": "Mostrar Número de Registros", + "SHOW_QR_CODE": "Mostrar Código QR", + "SIMPLE_QR": "Simple QR", + "STATE": "Estado", + "STREET": "Rua", + "SUCCESS": "Sucesso", + "SUPPORTED_BARCODE_TYPE": "Tipo de Código de Barras Suportado", + "SUPPORTED_TYPE": "Tipo Suportado", + "SYSTEM_DEFAULT": "Padrão do Sistema", + "TASK": "Tarefa", + "TASK_BUTTON_LAYOUT": "Layout dos Botões de Tarefa", + "TURN_OFF": "Desligar", + "TURN_ON": "Ligar", + "TURNED_OFF": "Desligado", + "TURNED_ON": "Ligado", + "TUTORIAL": "Tutorial", + "UNDO": "Desfazer", + "UNKNOWN": "Desconhecido", + "UPDATE_SUCCESSFULLY": "Atualizado com Sucesso", + "URL": "URL", + "VCARD_CONTACT": "Contato vCard", + "VERSION_VERSION": "Versão {version}", + "VIBRATION": "Vibração", + "VIEWED": "Visualizado", + "VIEW_BOOKMARK": "Visualizar Favorito", + "VIEW_GITHUB": "Visualizar GitHub", + "VIEW_LOG": "Visualizar Registro", + "VIEW_STORE_AND_SOURCE_CODE": "Visualizar Loja e Código-fonte", + "VIEW_INSTRUCTIONS": "Visualizar Instruções", + "WEBSITE": "Site", + "WIFI": "Wi-Fi", + "WIFI_ENCRYPTION": "Criptografia do Wi-Fi", + "WIFI_SSID": "SSID do Wi-Fi", + "WORK": "Trabalho", + "WORK_PHONE_NUMBER": "Número de Telefone do Trabalho", + "YAHOO_SEARCH": "Busca Yahoo!", + "YANDEX": "Yandex", + "YES": "Sim", + "MSG": { + "ALREADY_BOOKMARKED": "Falha! Já favoritado anteriormente", + "AUTO_KILL_BACKGROUND_EXPLAIN": "

Para economizar recursos do sistema e bateria, defina o tempo para encerrar automaticamente o aplicativo quando ele estiver em execução em segundo plano.


Se você escolher Seguir Configurações do Sistema, o aplicativo será totalmente controlado pelo sistema e não será encerrado por si próprio.


Observe que em algumas situações, o sistema pode interromper o aplicativo antecipadamente.

", + "AUTO_LOGGING_EXPLAIN": "Cada conteúdo de código QR e código de barras é registrado e armazenado automaticamente depois de escaneado, criado ou visualizado novamente. Você pode visualizá-los na página de Registro.", + "AUTO_MAX_BRIGHTNESS_EXPLAIN": "O brilho da tela é ajustado automaticamente para o máximo ao exibir um código QR.", + "AUTO_OPEN_URL_EXPLAIN": "Quando um código QR é escaneado e o conteúdo é uma URL, a URL será aberta automaticamente.", + "AUTO_SHOW_QR_EXPLAIN": "Pop-up automático de código QR na página de Resultado após as ações a seguir.", + "BACKUP_EXPLAIN": "Você pode fazer backup de todos os registros e favoritos localmente. Você receberá um conjunto de segredos após o backup. Mantenha o segredo com segurança, caso contrário, você não poderá restaurar o backup. Observe que o Simple QR não oferece suporte a backup e restauração entre plataformas diferentes.", + "BACKUP_FAILED": "Falha ao fazer backup", + "BACKUP_FAILED_2": "Falha ao fazer backup. Verifique se a permissão de armazenamento foi concedida.", + "BACKUP_SUCCESSFULLY": "

Backup feito com sucesso. Salve o arquivo de backup e mantenha o segunte segredo com segurança

{secret}

", + "BARCODE_TYPE": "Escanear
  • Código QR
  • Código de Barras 1D
  • Código Azteca
  • Matriz de Dados
  • PDF417
Importar Imagem
  • Código QR
Criar
  • Código QR
", + "BOOKMARKED": "Favoritado com Sucesso", + "BUTTON_DISPLAY_EXPLAIN": "Mostrar ou ocultar os botões de tarefa.", + "BUTTON_STYLE_EXPLAIN": "Escolha o estilo dos botões de tarefa.", + "CAMERA_PERMISSION": "Para habilitar a digitalização, você deve conceder a permissão de câmera.", + "CONTACT_PERMISSION": "Você deve conceder a permissão de Contatos para salvar o contato.", + "COPIED_SECRET": "Segredo copiado", + "COPY_TEXT": "Qual conteúdo você deseja copiar?", + "CREATE_QRCODE_MAX_LENGTH": "Máx. 1817 caracteres", + "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "O comprimento do conteúdo gerado do código QR não deve exceder 1817 caracteres.", + "DEBUG_MODE_ON": "Modo de Depuração ativado com sucesso", + "DELETE_OVERFLOWED_RECORDS": "Após sair desta página, os registros excedentes serão excluídos permanentemente.", + "EMAIL_MAX_LENGTH": "Máx. 254 caracteres", + "EMAIL_SUBJECT_MAX_LENGTH": "Máx. 78 caracteres", + "ERROR_CORRECTION_LEVEL_EXPLAIN": "

O código QR tem a capacidade de correção de erros para restaurar dados mesmo se o código estiver danificado.


Há 4 níveis disponíveis:

O nível L restaura 7% dos bytes de dados.

O nível M restaura 15% dos bytes de dados.

O nível Q restaura 25% dos bytes de dados.

O nível H restaura 30% dos bytes de dados.


Por favor, lembre-se de que elevar o nível pode melhorar a capacidade de correção de erros, mas também aumenta o tamanho do código QR. Portanto, para casos gerais, é recomendado o nível M.

", + "EXIT_APP": "

Tem certeza de que deseja sair do aplicativo?

Se você gosta do Simple QR, por favor, avalie-o na loja.

", + "EXPORT_TO_CSV_EXPLAIN": "Você pode exportar todos os registros e favoritos para um arquivo CSV.", + "FAILED_SAVING_CONTACT": "Falha ao salvar o contato", + "FAIL_PREPARE_SMS": "Falha ao enviar mensagem", + "IMPORT_FAILED": "Falha na importação", + "IMPORT_FROM_CSV_EXPLAIN": "Você pode importar registros e favoritos de um arquivo CSV com o formato definido pelo Simple QR. Se você deseja transferir dados entre Android e iOS, use essa função.", + "INPUT_TAG": "Por favor, dê uma etiqueta ao favorito", + "INVALID_BK_FILE": "Este não é um arquivo de backup válido.", + "INVALID_CSV_FILE": "Este não é um arquivo CSV válido.", + "NOT_BASE64_DE": "Os dados não podem ser decodificados em Base64", + "NOT_BASE64_EN": "Os dados não podem ser codificados em Base64", + "NOT_BASE64_EN_DE": "Os dados não podem ser codificados e decodificados em Base64", + "NO_QR_CODE": "Não é possível detectar o Código QR", + "ONLY_VCARD_3_0": "Apenas vCard 3.0 é suportado", + "PLEASE_INPUT_VALID_SECRET": "Por favor, insira um código válido", + "PORTRAIT_ONLY": "Retrato bloqueado", + "PREPARE_SMS": "Preparando mensagem", + "PREVIOUS_RECORDS": "Registros anteriores de escaneamento, criação ou visualização", + "QR_CODE_VALUE_NOT_EMPTY": "O valor do Código QR não pode estar vazio", + "READ_IMAGE_PERMISSION": "Você deve conceder permissão de Armazenamento para escanear imagem.", + "RECORDS_LIMIT_EXPLAIN": "Limite o número de registros que podem ser armazenados. O registro mais antigo será excluído quando o limite for atingido.", + "REMOVE_ALL_BOOKMARKS": "Tem certeza de que deseja remover todos os textos favoritados? Esta ação não pode ser desfeita.", + "REMOVE_ALL_RECORD": "Tem certeza de que deseja remover todos os registros? Esta ação não pode ser desfeita.", + "RESET_APP": "Tem certeza de que deseja redefinir este aplicativo e excluir todos os dados? Esta ação não pode ser desfeita.", + "RESET_DEFAULT": "Tem certeza de que deseja redefinir para o padrão?", + "RESTORE_EXPLAIN": "Você pode restaurar um backup anterior de registros e favoritos. Os itens do backup serão mesclados aos registros existentes. O tipo de arquivo de backup deve ser TFSQBK.", + "RESTORE_EXPLAIN_IOS": "Você pode restaurar um backup anterior de registros e favoritos. Os itens do backup serão mesclados aos registros existentes. O tipo de arquivo de backup deve ser ISQBK.", + "RESTORE_FAILED": "Falha ao restaurar", + "RESTORE_SECRET": "Por favor, insira o código para restaurar os registros", + "RESTORE_SUCCESSFUL": "Restaurado com sucesso", + "RESTORE_WRONG_SECRET": "Código incorreto", + "SAVED_CONTACT": "Contato salvo", + "SAVING_CONTACT": "Salvando contato", + "SCAN_QR_FROM_IMAGE": "O Simple QR utiliza o módulo \"cozmo/jsQR\" para escanear códigos QR a partir de imagens. Para escanear com sucesso,", + "SCAN_QR_FROM_IMAGE_R1": "Autorize o aplicativo a acessar o Armazenamento ou a Biblioteca de Fotos.", + "SCAN_QR_FROM_IMAGE_R2": "Certifique-se de que o plano de fundo da imagem selecionada não seja transparente. É recomendável escolher uma imagem que não seja capturada pela câmera.", + "SCAN_QR_FROM_IMAGE_R3": "Cancele a edição de fotos se desejar escanear a imagem inteira. (se aplicável)", + "SEARCH": "Qual conteúdo você deseja buscar?", + "SEARCH_ENGINE_EXPLAIN": "Escolha o mecanismo de busca para as pesquisas.", + "SHARE_QR": "Apenas escaneie!\n\nCompartilhado pelo Simple QR", + "SMS_MAX_LENGTH": "Máx. 160 caracteres", + "SSID_MAX_LENGTH": "Máx. 32 caracteres", + "START_PAGE_EXPLAIN": "Escolha a página inicial do aplicativo.", + "START_PAGE_HEADER_EXPLAIN": "Mostrar o cabeçalho do Simple QR na página inicial.", + "TAG_MAX_LENGTH": "Máx. 30 caracteres", + "TAG_MAX_LENGTH_EXPLAIN": "O comprimento da etiqueta não deve exceder 30 caracteres.", + "TUTORIAL_NOT_SHOW_AGAIN": "Não mostrar novamente", + "TUTORIAL_SWIPE_LEFT": "Deslize para a esquerda para excluir o registro correspondente.", + "TUTORIAL_SWIPE_RIGHT": "Deslize para a direita para marcar o texto do registro correspondente / editar a etiqueta do marcador correspondente.", + "UNDO_DELETE": "Você pode desfazer a exclusão", + "VIBRATION_EXPLAIN": "Forneça vibração ou feedback tátil. Observe que nem todos os dispositivos suportam essa funcionalidade." + }, + "BARCODE_TYPE": { + "1D": "Código de Barras 1D ", + "AZTEC": "Código Azteca ", + "DATA_MATRIX": "Matriz de Dados ", + "EAN": "Número de Artigo Europeu ", + "MAXICODE": "MaxiCode ", + "PDF_417": "PDF417 ", + "QR_CODE": "Código QR ", + "RSS": "GS1 DataBar ", + "UPC": "Código de Produto Universal " + }, + "UPDATE": { + "UPDATE_NOTES_ANDROID": "

Esta versão traz várias atualizações e novos recursos. Por favor, verifique o GitHub para obter detalhes.

", + "UPDATE_NOTES_IOS": "

Esta versão traz várias atualizações e novos recursos. Por favor, verifique o GitHub para obter detalhes.

" + } +} \ No newline at end of file diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json new file mode 100644 index 0000000..5eae99d --- /dev/null +++ b/src/assets/i18n/ru.json @@ -0,0 +1,322 @@ +{ + "100_RECORDS": "100 записей", + "30_RECORDS": "30 записей", + "49_DIGIT": "49-значный", + "50_RECORDS": "50 записей", + "ABOUT": "О приложении", + "ABOUT_SIMPLE_QR": "О Simple QR", + "ADD": "Добавить", + "ADD_BCC": "Добавить BCC", + "ADD_CC": "Добавить CC", + "ADD_CONTACT": "Добавить контакт", + "ADD_RECIPIENT": "Добавить получателя", + "APP": "Приложение", + "APPEARANCE_AND_EFFECTS": "Внешний вид и эффекты", + "APP_INITIAL_PAGE": "Стартовая страница", + "APP_VERSION": "Версия приложения", + "AT": "в", + "AT_LEAST_1_MINUTE_LATER": "Как минимум через минуту", + "AT_LEAST_3_MINUTES_LATER": "Как минимум через 3 минуты", + "AT_LEAST_5_MINUTES_LATER": "Как минимум через 5 минут", + "AUTO_KILL_BACKGROUND": "Фоновая активность", + "AUTO_LOGGING": "Автоматическое добавление в историю", + "AUTO_MAX_BRIGHTNESS": "Максимальная яркость", + "AUTO_OPEN_URL": "Открывать URL автоматически", + "AUTO_QR_CODE_POPUP": "Показывать QR-Код", + "BACKGROUND_COLOR": "Цвет фона", + "BACKING_UP": "Сохранение резервной копии", + "BACKUP": "Резервное копирование", + "BASE64": "Base64", + "BASE64_DECODED": "Декодировано из Base64", + "BASE64_ENCODED": "Закодировано в Base64", + "BCC": "BCC", + "BLACK": "Черная", + "BOOKMARK": "Закладки", + "BOOKMARKED": "Добавлено в закладки", + "BOOKMARKED_TEXTS": "Сохраненные тексты", + "BOOKMARKS": "Закладки", + "BRAVE_SEARCH": "Brave Search", + "BROWSE": "Просмотреть", + "BROWSER": "Браузер", + "BROWSE_WEBSITE": "Посетить веб-сайт", + "CALL": "Позвонить", + "CANCEL": "Отмена", + "CC": "CC", + "CITY": "Город", + "CLEAR": "Очистить", + "CLOSE": "Закрыть", + "COLOR": "Цвет", + "COLOR_THEME": "Тема", + "COMING_SOON": "Скоро...", + "CONTACT_METHOD": "Метод связи", + "CONTACT_NAME": "Имя контакта", + "CONTENT": "Содержание", + "CONTENT_TYPE": "Тип содержания", + "COPIED": "Скопировано", + "COPY": "Скопировать", + "COPY_SECRET_AND_SAVE_BACKUP": "Скопировать ключ и сохранить резервную копию", + "COPY_TEXT": "Скопировать текст", + "COUNTRY": "Страна", + "CREATE": "Создать", + "CREATED": "Создано", + "CREATE_QR_CODE": "Создать QR-Код", + "DARK": "Тёмная", + "DATE_OF_BIRTH": "Дата рождения", + "DEBUG_MODE": "Режим отладки", + "DECODING": "Декодирование", + "DECRYPTING": "Расшифровка", + "DETAILED": "Подробнее", + "DONE": "Готово", + "DUCK_DUCK_GO": "DuckDuckGo", + "ECOSIA": "Ecosia", + "EDIT": "Редактировать", + "EMAIL_ADDRESS": "Адрес электронной почты", + "EMAIL_BODY": "Содержание электронного письма", + "EMAIL_NTT_DOCOMO": "Электронное письмо (NTT Docomo)", + "EMAIL_RECIPIENT": "Получатель", + "EMAIL_SUBJECT": "Тема", + "EMAIL_TO": "Отправить по электронной почте", + "EMAIL_W3C_STANDARD": "Электронное письмо (стандарт W3C)", + "ENCRYPTING": "Шифрование", + "ERROR_CORRECTION_LEVEL": "Уровень коррекции ошибок", + "EXIT": "Выйти", + "EXIT_APP": "Выйти из приложения", + "EXPORT": "Экспорт", + "EXPORTING": "Экспортирование", + "EXPORT_TO_CSV": "Экспортировать в CSV", + "FAX_NUMBER": "Номер Fax", + "FEMALE": "Женский", + "FIRST_NAME": "Имя", + "FOLLOW_SYSTEM_SETTINGS": "Как в системе", + "FREE_TEXT": "Текст", + "FULL_RESET": "Полный сброс", + "FUNCTIONS": "Функции", + "GENDER": "Пол", + "GEOLOCATION": "Геолокация", + "GOOGLE_SEARCH": "Google Search", + "HAPTIC_FEEDBACK_ONLY": "Только тактильный отклик", + "HIDDEN_NETWORK_?": "Скрытая сеть?", + "HOME_ADDRESS": "Домашний адрес", + "HOME_PHONE_NUMBER": "Номер домашнего телефона", + "ICON_ONLY": "Только иконка", + "IMPORT": "Импорт", + "IMPORT_FROM_CSV": "Импортировать из CSV", + "IMPORT_IMAGE": "Импорт изображения", + "INITIAL_SEGMENT": "Стартовая страница", + "JOB_TITLE": "Должность", + "LANGUAGE": "Язык", + "LAST_NAME": "Фамилия", + "LATITUDE": "Широта", + "LEVEL_H": "Уровень H", + "LEVEL_L": "Уровень L", + "LEVEL_M": "Уровень M", + "LEVEL_Q": "Уровень Q", + "LIGHT": "Светлая", + "LOADING_DATA": "Загрузка информации", + "LOCK_LANDSCAPE": "Альбомная", + "LOCK_PORTRAIT": "Портретная", + "LOG": "История", + "LOG_BACKUP_AND_RESTORE": "История и восстановление", + "LONGITUDE": "Долгота", + "MALE": "Мужской", + "MANAGE_RECORDS": "Управление записями", + "MARGIN": "Отступ", + "MESSAGE": "Сообщение", + "MESSAGE_CONTENT": "Содержание сообщения", + "MICROSOFT_BING": "Microsoft Bing", + "MOBILE_PHONE_NUMBER": "Номер мобильного телефона", + "MORE": "Подробнее", + "NAME": "Имя", + "NO": "Нет", + "NONE": "None", + "NOT_AVAILABLE": "Нет доступа", + "NOT_PROVIDED": "Не предоставлено", + "NOT_TO_DISCLOSE": "Не указан", + "NO_LIMIT": "Нет ограничений", + "NUMBER_OF_RECORDS": "Количество записей", + "OK": "ОК", + "ONLY_DELETE_DATA": "Только удалить информацию", + "ONLY_RESET_SETTING": "Только сбросить настройки", + "OPEN": "Открыть", + "OPEN_URL": "Открыть URL", + "OPEN_WITH_...": "Открыть с помощью...", + "OPTIMIZING_DATA_...": "Оптимизация информации...", + "ORGANIZATION": "Организация", + "ORIGINAL": "Оригинал", + "OTHERS": "Другое", + "PASSWORD": "Пароль", + "PATCH_NOTES": "Нововведения", + "PERMISSION_REQUIRED": "Необходимо разрешение", + "PERSONAL": "Персональные данные", + "PHONE_NO": "Номер телефона", + "PHONE_NUMBER": "Номер телефона", + "PLEASE_WAIT": "Пожалуйста, подождите", + "POSTAL_CODE": "Почтовый индекс", + "PREPARING": "Подготовка", + "PREVIEW": "Предпросмотр", + "PRIVACY_POLICY": "Политика конфиденциальности", + "QR_CODE": "QR-Код", + "QR_CODE_AND_DECODED_RESULT": "QR-Код и результат", + "QR_CODE_CONTENT": "Содержание QR-Кода", + "QR_CODE_STYLE": "Стиль QR-Кода", + "RATE_THE_APP": "Оценить приложение", + "RECORDS_LIMIT": "Ограничение количества записей", + "REMOVE_ALL": "Удалить все", + "REMOVE_BCC": "Убрать BCC", + "REMOVE_CC": "Убрать CC", + "REMOVE_RECIPIENT": "Убрать получателя", + "REPORT_ISSUE": "Сообщить о проблеме", + "RESET_APP": "Сброс приложения", + "RESET_DEFAULT": "Настройки по умолчанию", + "RESTORE": "Восстановление", + "RESULT": "Результат", + "SAVE": "Сохранять", + "SAVED": "Сохранено", + "SCAN": "Сканировать", + "SCANNED": "Просканировано", + "SCANNING_FEEDBACK_ONLY": "Только отклик сканирования", + "SCAN_BY_CAMERA": "Сканирование с помощью камеры", + "SCREEN_ORIENTATION": "Ориентация экрана", + "SEARCH": "Поиск", + "SEARCH_ENGINE": "Поисковая система", + "SECRET": "Ключ", + "SEND": "Отправить", + "SEND_EMAIL": "Отправить электронное письмо", + "SEND_MESSAGE": "Отправить сообщение", + "SETTING": "Настройки", + "SETTINGS": "Настройки", + "SHARE": "Поделиться", + "SHARING": "Отправка", + "SHOW": "Показать", + "SHOW_NUMBER_OR_RECORDS": "Показывать количество записей", + "SHOW_QR_CODE": "Показать QR-Код", + "SIMPLE_QR": "Simple QR", + "STATE": "Округ", + "STREET": "Улица", + "SUCCESS": "Успешно", + "SUPPORTED_BARCODE_TYPE": "Поддерживаемые операции", + "SUPPORTED_TYPE": "Поддерживаемые операции", + "SYSTEM_DEFAULT": "Как в системе", + "TASK": "Задача", + "TASK_BUTTON_LAYOUT": "Кнопки действия", + "TURN_OFF": "Выключить", + "TURN_ON": "Включить", + "TURNED_OFF": "Выключено", + "TURNED_ON": "Включено", + "TUTORIAL": "Обучение", + "UNDO": "Отменить", + "UNKNOWN": "Неизвестно", + "UPDATE_SUCCESSFULLY": "Успешно обновлено", + "URL": "URL", + "VCARD_CONTACT": "Контакт vCard", + "VERSION_VERSION": "Версия {version}", + "VIBRATION": "Вибрация", + "VIEWED": "Просмотрено", + "VIEW_BOOKMARK": "Просмотр закладки", + "VIEW_GITHUB": "Посетить GitHub", + "VIEW_LOG": "Просмотр истории", + "VIEW_STORE_AND_SOURCE_CODE": "Посетить магазин и репозиторий с исходным кодом ", + "VIEW_INSTRUCTIONS": "Посмотреть обучение", + "WEBSITE": "Веб-сайт", + "WIFI": "WiFi", + "WIFI_ENCRYPTION": "Шифрование WiFi", + "WIFI_SSID": "WiFi SSID", + "WORK": "Место работы", + "WORK_PHONE_NUMBER": "Номер рабочего телефона", + "YAHOO_SEARCH": "Yahoo! Search", + "YANDEX": "Yandex", + "YES": "Да", + "MSG": { + "ALREADY_BOOKMARKED": "Ошибка! Уже добавлено в закладки ранее", + "AUTO_KILL_BACKGROUND_EXPLAIN": "

Чтобы использовать меньше системных ресурсов и батареи, выставьте количество времени, спустя которое приложение будет автоматически остановлено, если оно работает в фоне.


Если вы выберете опцию Как в системе, фоновая активность приложения будет полностью контролироваться системой, приложение не будет приостанавливать свою работу самостоятельно.


Заметьте, что в некоторых ситуациях система может останавливать приложение заблаговременно.

", + "AUTO_LOGGING_EXPLAIN": "Содержание каждого QR-Кода и Бар-Кода автоматически сохраняется после сканирования. Вы можете повторно обращаться к этим записям в разделе Истории.", + "AUTO_MAX_BRIGHTNESS_EXPLAIN": "Автоматически увеличивать яркость экрана до максимального значения во время просмотра QR-Кода.", + "AUTO_OPEN_URL_EXPLAIN": "Когда QR-код сканируется, а содержимое представляет собой URL-адрес, URL-адрес будет открыт автоматически.", + "AUTO_SHOW_QR_EXPLAIN": "Автоматически показывать QR-Код на странице результата после этих действий.", + "BACKUP_EXPLAIN": "Вы можете создать локальную резервную копию всех записей истории и закладок. Вам будет выдан набор ключей после резервного копирования. Сохраняйте эти ключи, иначе вы не сможете восстановить созданную резервную копию. Заметьте, что Simple QR не поддерживает резервное копирование между разными платформами.", + "BACKUP_FAILED": "Не удалось создать резервную копию", + "BACKUP_FAILED_2": "Не удалось создать резервную копию. Пожалуйста, убедитесь, что приложению выдано разрешение на взаимодействие с файлами.", + "BACKUP_SUCCESSFULLY": "

Резервное копирование успешно завершено. Пожалуйста, сохраните файл резервной копии и следующий ключ

{secret}

", + "BARCODE_TYPE": "Сканирование
  • QR-Код
  • Бар-Код
  • Код Aztec
  • Data Matrix
  • PDF417
Импорт изображения
  • QR-Код
Создание
  • QR-Код
", + "BOOKMARKED": "Успешно добавлено в закладки", + "BUTTON_DISPLAY_EXPLAIN": "Показать или скрыть кнопки действий.", + "BUTTON_STYLE_EXPLAIN": "Выберите стиль кнопок действий.", + "CAMERA_PERMISSION": "Чтобы приложение могло осуществлять сканирование, предоставьте доступ к Камере.", + "CONTACT_PERMISSION": "Для сохранения контакта, необходимо предоставить доступ к Контактам.", + "COPIED_SECRET": "Ключ скопирован", + "COPY_TEXT": "Какую часть контента вы бы хотели скопировать?", + "CREATE_QRCODE_MAX_LENGTH": "Максимум 1817 символов", + "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "Длина содержания сгенерированного QR-Кода не должна превышать 1817 символов.", + "DEBUG_MODE_ON": "Режим Отладки успешно активирован", + "DELETE_OVERFLOWED_RECORDS": "После того, как вы покинете эту страницу, лишние записи будут безвозвратно удалены.", + "EMAIL_MAX_LENGTH": "Максимум 254 символа", + "EMAIL_SUBJECT_MAX_LENGTH": "Максимум 78 символов", + "ERROR_CORRECTION_LEVEL_EXPLAIN": "

В структуру QR-Кодов заложена способность восстановления информации, даже если Код поврежден.

Существует 4 уровня такой защиты:

Уровень L восстанавливает 7% байт информации.

Уровень M восстанавливает 15% байт информации.

Уровень Q восстанавливает 25% байт информации.

Уровень H восстанавливает 30% байт информации.


Заметьте, что увеличение уровня защиты может увеличить вероятность восстановления информации, но также увеличивает размер QR-Кода. Поэтому, при обычных условиях рекомендуем использовать уровень M.

", + "EXIT_APP": "

Уверены, что хотите покинуть приложение?

Если вам нравится Simple QR, пожалуйста, оцените его в магазине приложений.

", + "EXPORT_TO_CSV_EXPLAIN": "Вы можете экспортировать все записи истории и закладки в CSV файл.", + "FAILED_SAVING_CONTACT": "Не удалось сохранить контакт", + "FAIL_PREPARE_SMS": "Не удалось отправить сообщение", + "IMPORT_FAILED": "Не удалось импортировать", + "IMPORT_FROM_CSV_EXPLAIN": "Вы можете импортировать записи истории и закладки из CSV файла в формате, заданном Simple QR. Если вы желаете переместить информацию между Android и iOS, воспользуйтесь этой функцией.", + "INPUT_TAG": "Пожалуйста, дайте закладке имя", + "INVALID_BK_FILE": "Это несовместимый файл резервной копии.", + "INVALID_CSV_FILE": "Это несовместимый CSV файл.", + "NOT_BASE64_DE": "Невозможно декодировать информацию из Base64", + "NOT_BASE64_EN": "Невозможно закодировать информацию в Base64", + "NOT_BASE64_EN_DE": "Невозможно закодировать или декодировать информацию Base64", + "NO_QR_CODE": "Не удалось распознать QR-Код", + "ONLY_VCARD_3_0": "Поддерживается только формат vCard 3.0", + "PLEASE_INPUT_VALID_SECRET": "Пожалуйста, введите правильный ключ", + "PORTRAIT_ONLY": "Портретная", + "PREPARE_SMS": "Подготовка сообщения", + "PREVIOUS_RECORDS": "Записи предыдущих действий: сканирования, создания или просмотра", + "QR_CODE_VALUE_NOT_EMPTY": "Содержание QR-Кода не может быть пустым", + "READ_IMAGE_PERMISSION": "Необходимо предоставить разрешение взаимодействовать с файлами, чтобы просканировать изображение.", + "RECORDS_LIMIT_EXPLAIN": "Укажите максимальное количество записей истории. Старые записи будут автоматически удалены при достижении указанного лимита.", + "REMOVE_ALL_BOOKMARKS": "Уверены, что хотите безвозвратно удалить все закладки?", + "REMOVE_ALL_RECORD": "Уверены, что хотите безвозвратно удалить все записи истории?", + "RESET_APP": "Уверены, что хотите вернуться к настройкам по умолчанию и удалить все данные приложения? Это действие необратимо.", + "RESET_DEFAULT": "Уверены, что хотите вернуться к настройкам по умолчанию?", + "RESTORE_EXPLAIN": "Вы можете восстановить закладки и записи истории из резервной копии. Полученные данные будут совмещены с текущими данными приложения. Файл резервной копии должен иметь тип TFSQBK.", + "RESTORE_EXPLAIN_IOS": "Вы можете восстановить закладки и записи истории из резервной копии. Полученные данные будут совмещены с текущими данными приложения. Файл резервной копии должен иметь тип ISQBK.", + "RESTORE_FAILED": "Не удалось восстановить", + "RESTORE_SECRET": "Пожалуйста, введите ключ для восстановления данных", + "RESTORE_SUCCESSFUL": "Восстановление успешно завершено", + "RESTORE_WRONG_SECRET": "Неверный ключ", + "SAVED_CONTACT": "Контакт сохранен", + "SAVING_CONTACT": "Идет сохранение контакта", + "SCAN_QR_FROM_IMAGE": "Simple QR использует модуль \"cozmo/jsQR\", чтобы сканировать QR-Код из изображение. Чтобы успешно осуществить сканирование", + "SCAN_QR_FROM_IMAGE_R1": "Дайте приложению разрешение на доступ к Памяти устройства или Фото на нем.", + "SCAN_QR_FROM_IMAGE_R2": "Убедитесь, что фон изображения не прозрачный. Не рекомендовано использовать изображение, полученные не при помощи камеры.", + "SCAN_QR_FROM_IMAGE_R3": "Отмените редактирование фото, если вы хотите просканировать изображение целиком (по ситуации).", + "SEARCH": "Что конкретно вы хотите найти?", + "SEARCH_ENGINE_EXPLAIN": "Выберите поисковую систему для осуществления поиска после получения результата.", + "SHARE_QR": "Скорее сканируй!\n\nОтправлено при помощи Simple QR", + "SMS_MAX_LENGTH": "Максимум 160 символов", + "SSID_MAX_LENGTH": "Максимум 32 символа", + "START_PAGE_EXPLAIN": "Выберете стартовую страницу приложения.", + "START_PAGE_HEADER_EXPLAIN": "Показывать заголовок Simple QR на стартовой странице.", + "TAG_MAX_LENGTH": "Максимум 30 символов", + "TAG_MAX_LENGTH_EXPLAIN": "Длина имени не должна превышать 30 символов..", + "TUTORIAL_NOT_SHOW_AGAIN": "Не показывать снова", + "TUTORIAL_SWIPE_LEFT": "Смахните влево, чтобы удалить соответствующую запись.", + "TUTORIAL_SWIPE_RIGHT": "Смахните вправо, чтобы добавить текст соответствующей записи в закладки / редактировать имя соответствующей закладки.", + "UNDO_DELETE": "Вы можете отменить удаление", + "VIBRATION_EXPLAIN": "Использовать вибрацию или тактильный отклик. Заметьте, что не все устройства поддерживают эту функцию." + }, + "BARCODE_TYPE": { + "1D": "Бар-Код ", + "AZTEC": "Код Aztec ", + "DATA_MATRIX": "Data Matrix ", + "EAN": "European Article Number ", + "MAXICODE": "MaxiCode ", + "PDF_417": "PDF417 ", + "QR_CODE": "QR-Код ", + "RSS": "GS1 DataBar ", + "UPC": "Universal Product Code " + }, + "UPDATE": { + "UPDATE_NOTES_ANDROID": "

В этом релизе вас ждет несколько обновлений и новых функций. Посетите GitHub проекта, чтобы узнать детали.

", + "UPDATE_NOTES_IOS": "

В этом релизе вас ждет несколько обновлений и новых функций. Посетите GitHub проекта, чтобы узнать детали.

" + } +} \ No newline at end of file diff --git a/src/assets/i18n/zh-CN.json b/src/assets/i18n/zh-CN.json index 76a706f..bf8df56 100644 --- a/src/assets/i18n/zh-CN.json +++ b/src/assets/i18n/zh-CN.json @@ -1,275 +1,322 @@ { - "SIMPLE_QR": "简易QR", - "MESSAGE": "信息", - "UPDATE_NOTES": "版本内容", - "PERMISSION_REQUIRED": "需要授权", - "CAMERA_PAUSED": "扫描已暂停", - "CREATE_QRCODE": "建立 QR 码", - "CREATE_QRCODE_MAX_LENGTH": "最多 1817 个字元", - "QRCODE_CONTENT": "QR 码内容", - "CONTENT": "内容", - "EXIT_APP": "离开应用程式", - "WEBSITE": "网站", - "PHONE_CALL": "通话", - "SEARCH": "搜索", - "COPY": "复制", - "OPEN": "开启", - "YES": "是", - "NO": "否", - "OK": "好的", + "100_RECORDS": "100 笔记录", + "30_RECORDS": "30 笔记录", + "49_DIGIT": "49 位", + "50_RECORDS": "50 笔记录", "ABOUT": "关于", - "ENTER": "输入", - "CONFIRM": "确定", - "CANCEL": "取消", - "RESUME": "恢复", - "ORIGINAL": "原始", - "BASE64": "Base64", - "BASE64_ENCODED": "Base64 编码", - "BASE64_DECODED": "Base64 解码", - "HISTORY": "历史", - "TUTORIAL": "教学", - "TUTORIAL_TOUCH_HISTORY": "轻触此图示可查看您过往的扫描记录", - "TUTORIAL_TOUCH_BOOKMARK": "轻触此图示可查看您收藏的书签", - "TUTORIAL_SWIPE_LEFT": "向左划项目可删除相关记录", - "TUTORIAL_SWIPE_RIGHT": "向右划项目可将相关记录的文字加入书签", - "TUTORIAL_NOT_SHOW_AGAIN": "下次不再提醒", - "BOOKMARKS": "书签", - "SCANNING_HISTORY_SHOWN": "您的扫描记录会在这里显示", - "FAVOURITE_TEXT_SHOWN": "已收藏的书签会在这里显示", - "REMOVE_ALL": "删除全部", - "RESULT": "结果", - "UNDO": "复原", - "PHONE_NUMBER": "电话号码", - "MESSAGE_CONTENT": "信息内容", - "EMAIL_TO": "电邮至", - "CC": "副本", - "BCC": "密件副本", - "EMAIL_SUBJECT": "电邮主题", - "EMAIL_BODY": "电邮内容", - "CONTACT_NAME": "联络人名称", - "BARCODE_CONTENT": "条码内容", - "BARCODE_FORMAT": "条码格式", - "BROWSE_WEBSITE": "浏览网站", + "ABOUT_SIMPLE_QR": "关于简易QR", + "ADD": "新增", + "ADD_BCC": "新增密件副本", + "ADD_CC": "新增副本", "ADD_CONTACT": "新增联络人", + "ADD_RECIPIENT": "新增收件者", + "APP": "应用程序", + "APPEARANCE_AND_EFFECTS": "外观与效果", + "APP_INITIAL_PAGE": "程序起始页面", + "APP_VERSION": "程序版本", + "AT": "于", + "AT_LEAST_1_MINUTE_LATER": "最早 1 分钟后", + "AT_LEAST_3_MINUTES_LATER": "最早 3 分钟后", + "AT_LEAST_5_MINUTES_LATER": "最早 5 分钟后", + "AUTO_KILL_BACKGROUND": "自动停止背景执行", + "AUTO_LOGGING": "自动记录", + "AUTO_MAX_BRIGHTNESS": "自动最大亮度", + "AUTO_OPEN_URL": "自动打开网址", + "AUTO_QR_CODE_POPUP": "自动弹出 QR 码", + "BACKGROUND_COLOR": "背景颜色", + "BACKING_UP": "备份中", + "BACKUP": "备份", + "BASE64": "Base64", + "BASE64_DECODED": "Base64 解码", + "BASE64_ENCODED": "Base64 编码", + "BCC": "密件副本", + "BLACK": "黑色", + "BOOKMARK": "书签", + "BOOKMARKED": "已书签", + "BOOKMARKED_TEXTS": "已收藏的书签", + "BOOKMARKS": "书签", + "BRAVE_SEARCH": "Brave Search", + "BROWSE": "浏览", + "BROWSER": "浏览器", + "BROWSE_WEBSITE": "浏览网站", "CALL": "致电", - "SEND_MESSAGE": "传送信息", - "SEND_EMAIL": "传送电邮", - "CONNECT_WIFI": "连接 WiFi 网络", - "WIFI_NETWORK": "WiFi 网络", - "CONNECT": "连接", - "WIFI_SSID": "WiFi 名称", - "WIFI_ENCRYPTION": "安全性", - "WIFI_HIDDEN": "隐藏的网络?", - "NOT_AVAILABLE": "没有", - "NOT_PROVIDED": "(没有提供)", - "WEB_SEARCH": "搜索", + "CANCEL": "取消", + "CC": "副本", + "CITY": "城市", + "CLEAR": "清除", + "CLOSE": "关闭", + "COLOR": "颜色", + "COLOR_THEME": "颜色主题", + "COMING_SOON": "即将推出", + "CONTACT_METHOD": "联络方法", + "CONTACT_NAME": "联络人名称", + "CONTENT": "内容", + "CONTENT_TYPE": "内容类别", + "COPIED": "已复制", + "COPY": "复制", + "COPY_SECRET_AND_SAVE_BACKUP": "复制密码並储存备份", "COPY_TEXT": "复制内容", - "SHARE_QRCODE": "分享 QR 码", + "COUNTRY": "国家", + "CREATE": "建立", + "CREATED": "建立", + "CREATE_QR_CODE": "建立 QR 码", + "DARK": "深色", + "DATE_OF_BIRTH": "出生日期", + "DEBUG_MODE": "除错模式", + "DECODING": "解码中", + "DECRYPTING": "解密中", + "DETAILED": "详细说明", + "DONE": "完成", + "DUCK_DUCK_GO": "DuckDuckGo", + "ECOSIA": "Ecosia", + "EDIT": "修改", + "EMAIL_ADDRESS": "电邮地址", + "EMAIL_BODY": "电邮内容", + "EMAIL_NTT_DOCOMO": "电邮 (NTT Docomo)", + "EMAIL_RECIPIENT": "收件者", + "EMAIL_SUBJECT": "电邮主题", + "EMAIL_TO": "电邮至", + "EMAIL_W3C_STANDARD": "电邮 (W3C 标准)", + "ENCRYPTING": "加密中", + "ERROR_CORRECTION_LEVEL": "容错等级", + "EXIT": "离开", + "EXIT_APP": "离开程序", + "EXPORT": "导出", + "EXPORTING": "导出中", + "EXPORT_TO_CSV": "导出到CSV", + "FAX_NUMBER": "传真号码", + "FEMALE": "女性", + "FIRST_NAME": "名字", + "FOLLOW_SYSTEM_SETTINGS": "由系统控制", + "FREE_TEXT": "自由文本", + "FULL_RESET": "完整重设", + "FUNCTIONS": "功能", + "GENDER": "性别", + "GEOLOCATION": "地理定位", + "GOOGLE_SEARCH": "Google 搜索", + "HAPTIC_FEEDBACK_ONLY": "仅触感反馈", + "HIDDEN_NETWORK_?": "隐藏的网络?", + "HOME_ADDRESS": "家居地址", + "HOME_PHONE_NUMBER": "家居电话号码", + "ICON_ONLY": "仅限图示", + "IMPORT": "导入", + "IMPORT_FROM_CSV": "从CSV导入", + "IMPORT_IMAGE": "导入图片", + "INITIAL_SEGMENT": "起始分页", + "JOB_TITLE": "职位名称", + "LANGUAGE": "语言", + "LAST_NAME": "姓氏", + "LATITUDE": "纬度", + "LEVEL_H": "H 等级", + "LEVEL_L": "L 等级", + "LEVEL_M": "M 等级", + "LEVEL_Q": "Q 等级", + "LIGHT": "浅色", + "LOADING_DATA": "载入中", + "LOCK_LANDSCAPE": "锁定横向", + "LOCK_PORTRAIT": "锁定纵向", + "LOG": "记录", + "LOG_BACKUP_AND_RESTORE": "记录、备份与还原", + "LONGITUDE": "经度", + "MALE": "男性", + "MANAGE_RECORDS": "管理记录", + "MARGIN": "边距", + "MESSAGE": "信息", + "MESSAGE_CONTENT": "信息内容", + "MICROSOFT_BING": "Microsoft Bing", + "MOBILE_PHONE_NUMBER": "手提电话号码", + "MORE": "更多", + "NAME": "姓名", + "NO": "否", + "NONE": "没有", + "NOT_AVAILABLE": "没有", + "NOT_PROVIDED": "没有提供", + "NOT_TO_DISCLOSE": "不愿透露", + "NO_LIMIT": "无上限", + "NUMBER_OF_RECORDS": "记录数量", + "OK": "知道", + "ONLY_DELETE_DATA": "只删除数据", + "ONLY_RESET_SETTING": "只重设设定", + "OPEN": "打开", + "OPEN_URL": "打开网址", + "OPEN_WITH_...": "以下方式打开", + "OPTIMIZING_DATA_...": "优化数据...", + "ORGANIZATION": "组织", + "ORIGINAL": "原始", + "OTHERS": "其他", + "PASSWORD": "密码", + "PATCH_NOTES": "版本内容", + "PERMISSION_REQUIRED": "需要授权", + "PERSONAL": "个人", + "PHONE_NO": "电话号码", + "PHONE_NUMBER": "电话号码", + "PLEASE_WAIT": "请稍候", + "POSTAL_CODE": "邮政编码", + "PREPARING": "准备中", + "PREVIEW": "预览", + "PRIVACY_POLICY": "私隐政策", + "QR_CODE": "QR 码", + "QR_CODE_AND_DECODED_RESULT": "QR 码及解码结果", + "QR_CODE_CONTENT": "QR 码内容", + "QR_CODE_STYLE": "QR 码样式", + "RATE_THE_APP": "前往商店评分", + "RECORDS_LIMIT": "记录上限", + "REMOVE_ALL": "删除全部", + "REMOVE_BCC": "删除密件副本", + "REMOVE_CC": "删除副本", + "REMOVE_RECIPIENT": "删除收件者", + "REPORT_ISSUE": "回报问题", + "RESET_APP": "重设程序", + "RESET_DEFAULT": "还原预设值", + "RESTORE": "还原", + "RESULT": "结果", + "SAVE": "储存", + "SAVED": "已储存", + "SCAN": "扫描", + "SCANNED": "扫描", + "SCANNING_FEEDBACK_ONLY": "仅扫描反馈", + "SCAN_BY_CAMERA": "相机扫描", + "SCREEN_ORIENTATION": "萤幕方向", + "SEARCH": "搜索", + "SEARCH_ENGINE": "搜索引擎", + "SECRET": "密码", + "SEND": "传送", + "SEND_EMAIL": "传送电邮", + "SEND_MESSAGE": "传送信息", "SETTING": "设定", "SETTINGS": "设定", - "LANGUAGE": "语言", - "SYSTEM_DEFAULT": "系统预设", - "COLOR_THEME": "颜色主题", - "LIGHT": "浅色", - "DARK": "深色", - "BLACK": "黑色", - "CAMERA_PAUSE_TRIGGER": "暂停扫描触发", - "NEVER": "永不", - "SECONDS": "秒", - "CAMERA_PAUSE_EXPLAIN": "扫描进行时,程式会检测设备的运动。如果您的设备静止一段时间,程式将会暂停扫描和相机预览以节省电力。选择「永不」可停用这功能。 ", - "LOGGING_BACKUP_RESTORE": "记录、备份与还原", - "TURN_ON": "开启", - "TURN_OFF": "关闭", - "AUTO_LOGGING": "自动记录", - "AUTO_LOGGING_EXPLAIN": "在您扫描、建立或重新浏览记录时,条码内容会自动被记录并储存在本地储存空间。您可在「历史」页面浏览。 ", - "BACKUP_AND_RESTORE": "备份与还原", - "BACKUP": "备份", - "RESTORE": "还原", - "BACKUP_EXPLAIN": "您可以为现有的扫描记录及书签进行本地备份。备份后,您会收到一组密码。请妥善保存密码,否则您无法还原此备份。请注意,简易QR并不支援跨平台备份与还原。备份时,请允许简易QR存取储存空间。", - "RESTORE_EXPLAIN": "您可以还原先前的扫描记录及书签备份,还原后备份项目会与现有记录合并。备份档案该以TFSQBK作为档名结尾。", - "RESTORE_EXPLAIN_IOS": "您可以还原先前的扫描记录及书签备份,还原后备份项目会与现有记录合并。备份档案该以ISQBK作为档名结尾。", - "COPY_AND_BACKUP": "复制并开始备份", - "ENCRYPTING": "加密中...", - "BACKING_UP": "备份中...", - "SECRET": "密码", - "LENGTH_49": "49 位", - "LENGTH_32": "32 位", - "LENGTH_16": "16 位", - "COPY_SECRET": "只复制密码", - "COPY_SECRET_SHARE": "复制密码並储存备份", - "TURN_ON_HAPTIC": "仅触感反馈", - "TURN_ON_SCANNED": "仅扫描反馈", - "SEARCH_ENGINE": "搜索引擎", - "GOOGLE": "Google 搜索", - "BING": "Microsoft Bing", - "YAHOO": "Yahoo! 雅虎搜索", - "DUCK_DUCK_GO": "DuckDuckGo", - "SEARCG_ENGINE_EXPLAIN": "设定搜索引擎,以便在结果页面进行搜索。", - "ABOUT_SIMPLE_QR": "关于简易QR", - "CHECK_GOOGLE_PLAY": "查看 Google Play", - "CHECK_APP_STORE": "查看 App Store", - "PRIVACY_POLICY": "私隐政策", - "SUPPORT_DEVELOPER": "支持开发人员", - "WATCH_ADS": "观看广告", - "THANKS_SUPPORT": "广告载入中,感谢支持", - "DONATE_PAYPAL": "透过 PayPal 捐款", - "SEE_MORE": "查看更多", - "SUCCESS": "成功", - "RESTORED": "已恢复", - "SUPPORT_DEV_EXPLAIN": "简易 QR 是一个免费的开源应用程式,欢迎您查阅源代码及作出贡献。", - "DEVELOPING": "开发中", - "REPORT_ISSUE": "回报问题", - "RESET_APP": "重设应用程式", - "APP_VERSION": "程式版本", - "NEW_CONTACT": "新联络人", - "SAVE": "储存", - "REQUIRED": "必需", - "NEW_CONTACT_TIP": "只有名称、电话号码及电邮地址会被储存", - "FIRST_NAME": "名字", - "LAST_NAME": "姓氏", - "PHONE_TAG": "电话标签", - "EMAIL_ADDRESS": "电邮地址", - "EMAIL_TAG": "电邮标签", - "DEFAULT_OR_OTHER": "预设 / 其他", - "MOBILE": "手提", - "HOME": "家居", - "WORK": "工作", - "PREPARING": "准备中", - "PLEASE_WAIT": "请稍候...", - "DECODING": "解码中...", - "CHECK_PERMISSION": "检查权限", - "CHECK_WIFI": "检查 WiFi 开关", - "RECOGNIZE_NETWORK": "识别中", - "CONNECTING_NETWORK": "连接中", - "CONTENT_TYPE": "内容类别", - "FREE_TEXT": "任意文字", - "URL": "网址", - "VCARD_CONTACT": "vCard 联络人", - "PHONE": "电话号码", - "SMS": "信息", - "EMAIL": "电邮", - "WIFI": "WiFi", - "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "建立的 QR 码内容长度不能多于 1817 个字元", - "CREATE": "建立", - "CLEAR": "清除", - "EMAIL_RECIPIENT": "收件者", - "EMAIL_MAX_LENGTH": "最多 254 个字元", - "ADD_RECIPIENT": "新增收件者", - "REMOVE_RECIPIENT": "删除收件者", - "ADD_CC": "新增副本", - "REMOVE_CC": "删除副本", - "ADD_BCC": "新增密件副本", - "REMOVE_BCC": "删除密件副本", - "EMAIL_SUBJECT_MAX_LENGTH": "最多 78 个字元", - "SMS_MAX_LENGTH": "最多 160 个字元", - "NOT_TO_DISCLOSE": "不愿透露", - "MALE": "男性", - "FEMALE": "女性", - "NAME": "姓名", - "CONTACT_METHOD": "联络方法", - "MOBILE_PHONE_NUMBER": "手提电话号码", - "HOME_PHONE_NUMBER": "家居电话号码", - "WORK_PHONE_NUMBER": "工作联络号码", - "FAX_NUMBER": "传真号码", - "ORGANIZATION": "组织", - "JOB_TITLE": "职位名称", - "HOME_ADDRESS": "家居地址", - "STREET": "街道", - "CITY": "城市", + "SHARE": "分享", + "SHARING": "正在分享", + "SHOW": "显示", + "SHOW_NUMBER_OR_RECORDS": "显示记录数量", + "SHOW_QR_CODE": "显示 QR 码", + "SIMPLE_QR": "简易QR", "STATE": "州", - "POSTAL_CODE": "邮政编码", - "COUNTRY": "国家", - "PERSONAL": "个人", - "BIRTHDAY": "出生日期", - "GENDER": "性别", - "SSID_MAX_LENGTH": "最多 32 个字元", - "NONE": "没有", - "PASSWORD": "密码", - "SCAN": "扫描", - "IMPORT": "汇入", - "IMPORT_IMAGE": "汇入图片", - "CHECK_MODULE": "查阅模组", - "VIEW_SOURCE": "查阅源代码", - "TURN_ON_TORCH": "开启电筒", - "TURN_OFF_TORCH": "关闭电筒", - "VIBRATION": "震动效果", - "CLOSE": "关闭", + "STREET": "街道", + "SUCCESS": "成功", "SUPPORTED_BARCODE_TYPE": "支援的条码类别", "SUPPORTED_TYPE": "支援的类别", - "DEBUG_MODE": "除错模式", + "SYSTEM_DEFAULT": "系统预设", + "TASK": "行动", + "TASK_BUTTON_LAYOUT": "行动按键布局", + "TURN_OFF": "关闭", + "TURN_ON": "开启", + "TURNED_OFF": "已关闭", + "TURNED_ON": "已开启", + "TUTORIAL": "教学", + "UNDO": "复原", + "UNKNOWN": "未知", + "UPDATE_SUCCESSFULLY": "更新成功", + "URL": "网址", + "VCARD_CONTACT": "vCard 联络人", + "VERSION_VERSION": "版本 {version}", + "VIBRATION": "震动效果", + "VIEWED": "查看", + "VIEW_BOOKMARK": "查看书签", + "VIEW_GITHUB": "查看 GitHub", + "VIEW_LOG": "查看记录", + "VIEW_STORE_AND_SOURCE_CODE": "查看商店及源代码", + "VIEW_INSTRUCTIONS": "查看说明", + "WEBSITE": "网站", + "WIFI": "WiFi", + "WIFI_ENCRYPTION": "安全性", + "WIFI_SSID": "WiFi 名称", + "WORK": "工作", + "WORK_PHONE_NUMBER": "工作联络号码", + "YAHOO_SEARCH": "Yahoo! 雅虎搜索", + "YANDEX": "Yandex", + "YES": "是", "MSG": { - "CALL_PHONE": "确定要致电 {phoneNumber} ?", - "FAIL_CALL_PHONE": "无法开启电话", - "PREPARE_SMS": "准备信息中", - "FAIL_PREPARE_SMS": "无法传送信息", - "SEARCH": "您想搜索哪一个内容?", - "COPY_TEXT": "您想复制哪一个内容?", - "COPIED": "已复制", - "DECODED": "已解码", - "NOT_BASE64_EN_DE": "内容不能被 Base64 编码及解码", - "NOT_BASE64_EN": "内容不能被 Base64 编码", - "NOT_BASE64_DE": "内容不能被 Base64 解码", - "SHARE_QR": "扫一扫!\n\n从「简易 QR」分享", - "SAVED": "已储存", - "EXIT_APP": "确定要离开?", - "CAMERA_PERMISSION_1": "按「设定」以授权「简易QR」使用相机。 ", - "CAMERA_PERMISSION_2": "使用扫描功能,您必须授权「简易QR」使用相机。 ", - "READ_IMAGE_PERMISSION": "要扫描图片,您必须授权「简易QR」存取储存空间。 ", - "CAMERA_PAUSED": "为节省电力已暂停扫描,提起装置或按「恢复」可继续使用。

在「设定」可调校此功能。 ", - "UNDO_DELETE": "您可在数秒内复原记录", - "RESET_APP": "确定要重设应用程式和删除所有数据? 此动作无法被撤回。", - "REMOVE_ALL_RECORD": "确定要删除全部记录? 此动作无法被撤回。", - "GIVE_NAME_CONTACT": "请填写名字", - "FAIL_START_ADS": "无法初始化广告", - "FAIL_LOAD_ADS": "无法载入广告", - "FAIL_SHOW_ADS": "无法显示广告", - "ALREADY_BOOKMARKED": "已加入书签", - "BOOKMARKED": "成功加入书签", - "UNBOOKMARKED": "已移除书签", - "REMOVE_ALL_BOOKMARKS": "确定要删除所有书签? 此动作无法被撤回。", - "WIFI_NO_SSID": "没有提供 WiFi 名称", - "WIFI_PERMISSION": "必须授权使用位置资讯才能连接 WiFi", - "TURN_ON_WIFI": "请开启 WiFi 功能", - "TURN_ON_LOCATION": "请开启位置资讯", - "WIFI_NOT_FOUND": "您的位置不在此网络覆盖范围内", - "FAIL_CONNECT_WIFI": "无法连接此网络", - "WIFI_NO_INTERNET": "此网络无互联网连接", - "ANDROID_WIFI_NATIVE_SETTINGS": "基于 Android 政策,我们建议您使用原生设定来扫描此 QR 码以连接网络。", - "QR_CODE_VALUE_NOT_EMPTY": "QR 码不能是空值", - "NO_QR_CODE": "此图片没有 QR 码!请确保图片背景不是透明", - "IMAGE_NO_TRANSPARENT": "请确保图片背景不是透明", - "SCAN_QR_FROM_IMAGE": "简易 QR 使用「cozmo/jsQR」模组实现汇入图片扫描 QR 码的功能。请确保汇入图片的背景并非透明,否则模组无法扫描图片里的 QR 码。汇入图片时请授权简易 QR 存取储存空间。", - "BARCODE_TYPE": "扫描
  • QR 码
  • 一维条码
  • Aztec 码
  • 数据矩阵码
  • PDF417 条码
汇入图片
  • QR 码
建立
  • QR 码
", - "TAP_TO_ZOOM_IN": "轻触以放大", - "ONLY_VCARD_3_0": "只支援 vCard 3.0", - "DEBUG_MODE_ON": "成功开启除错模式", + "ALREADY_BOOKMARKED": "无法新增重复书签", + "AUTO_KILL_BACKGROUND_EXPLAIN": "

为节省系统资源及减低耗电,当程序进入背景执行时,设定自动停止执行程序及所有背景活动的时间。


若选择由系统控制,程序本身将不会干预其运作及资源占用,全由系统控制。


请注意,在某些情况下系统可能会提前停止程序的背景运作。

", + "AUTO_LOGGING_EXPLAIN": "在您扫描、建立或查看记录时,条码内容会自动被记录并储存在本地储存空间。您可在「记录」页面浏览所有记录。", + "AUTO_MAX_BRIGHTNESS_EXPLAIN": "当显示 QR 码时,自动调校萤幕亮度到最大。", + "AUTO_OPEN_URL_EXPLAIN": "扫描QR码且内容为URL时,会自动打开该URL。", + "AUTO_SHOW_QR_EXPLAIN": "在进行以下已选择的动作后,自动弹出 QR 码。", + "BACKUP_EXPLAIN": "您可以为现有的扫描记录及书签进行本地备份。备份后,您会得到一组密码。请妥善保存密码,否则您无法还原此备份。请注意,简易QR并不支援跨平台备份与还原。", "BACKUP_FAILED": "备份失败", "BACKUP_FAILED_2": "备份失败。请确保已允许「简易QR」存取储存空间", "BACKUP_SUCCESSFULLY": "

成功备份。请妥善保存备份档及以下密码

{secret}

", - "RESTORE_SECRET": "请输入密码以还原备份", - "INVALID_BK_FILE": "这不是有效的备份档", - "RESTORE_FAILED": "还原失败", - "PLEASE_INPUT_VALID_SECRET": "请输入有效密码", - "RESTORE_WRONG_SECRET": "密码错误", - "RESTORE_SUCCESSFUL": "还原成功", + "BARCODE_TYPE": "扫描
  • QR 码
  • 一维条码
  • Aztec 码
  • 数据矩阵码
  • PDF417 条码
导入图片
  • QR 码
建立
  • QR 码
", + "BOOKMARKED": "成功加入书签", + "BUTTON_DISPLAY_EXPLAIN": "显示或隐藏行动按键。", + "BUTTON_STYLE_EXPLAIN": "选择行动按键的样式。", + "CAMERA_PERMISSION": "要使用扫描功能,您必须授权「简易QR」使用相机。 ", + "CONTACT_PERMISSION": "要新增联络人,您必须授权「简易QR」存取通讯录。", "COPIED_SECRET": "已复制密码", - "SAVED_CONTACT": "已新增联络人", + "COPY_TEXT": "您想复制哪一个内容?", + "CREATE_QRCODE_MAX_LENGTH": "最多 1817 个字元", + "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "建立的 QR 码内容长度不能多于 1817 个字元。", + "DEBUG_MODE_ON": "成功开启除错模式", + "DELETE_OVERFLOWED_RECORDS": "离开本页面后,溢出的记录会被永久删除。", + "EMAIL_MAX_LENGTH": "最多 254 个字元", + "EMAIL_SUBJECT_MAX_LENGTH": "最多 78 个字元", + "ERROR_CORRECTION_LEVEL_EXPLAIN": "

QR 码具有容错能力,即使图形有破损,仍然可以被机器读取内容。


容错率分为四个等级:

L 等级可容错 7% 的字码;

M 等级可容错 15% 的字码;

Q 等级可容错 25% 的字码;

H 等级可容错 30% 的字码。


容错率愈高,QR 码图形面积则愈大。因此,在一般情况下建议使用 M 等级。

", + "EXIT_APP": "

确定要离开?

若您喜欢简易QR,欢迎前往商店评分。

", + "EXPORT_TO_CSV_EXPLAIN": "您可以将所有扫描记录及书签导出到CSV档案。", "FAILED_SAVING_CONTACT": "无法新增联络人", - "CONTACT_PERMISSION": "要新增联络人,您必须授权「简易QR」存取通讯录。 " + "FAIL_PREPARE_SMS": "无法传送信息", + "IMPORT_FAILED": "导入失敗", + "IMPORT_FROM_CSV_EXPLAIN": "您可以从简易QR定义的CSV档案导入记录和书签。若您想在Android和iOS设备之间传输数据,请使用此功能。", + "INPUT_TAG": "请为此书签输入一个标签", + "INVALID_BK_FILE": "这不是有效的备份档", + "INVALID_CSV_FILE": "这不是有效的CSV档", + "NOT_BASE64_DE": "内容不能被 Base64 解码", + "NOT_BASE64_EN": "内容不能被 Base64 编码", + "NOT_BASE64_EN_DE": "内容不能被 Base64 编码及解码", + "NO_QR_CODE": "此图片没有 QR 码", + "ONLY_VCARD_3_0": "只支援 vCard 3.0", + "PLEASE_INPUT_VALID_SECRET": "请输入有效密码", + "PORTRAIT_ONLY": "已锁定纵向", + "PREPARE_SMS": "准备信息中", + "PREVIOUS_RECORDS": "过往扫描、建立或查看的记录", + "QR_CODE_VALUE_NOT_EMPTY": "QR 码不能是空值", + "READ_IMAGE_PERMISSION": "要扫描图片,您必须授权「简易QR」存取储存空间。 ", + "RECORDS_LIMIT_EXPLAIN": "限制记录的数量,超过上限时旧记录会被删除。", + "REMOVE_ALL_BOOKMARKS": "确定要删除所有书签? 此动作无法被撤回。", + "REMOVE_ALL_RECORD": "确定要删除全部记录? 此动作无法被撤回。", + "RESET_APP": "确定要重设应用程序和删除所有数据? 此动作无法被撤回。", + "RESET_DEFAULT": "确定要还原预设值?", + "RESTORE_EXPLAIN": "您可以还原先前的扫描记录及书签备份,还原后备份项目会与现有记录合并。备份档案该以TFSQBK作为档名结尾。", + "RESTORE_EXPLAIN_IOS": "您可以还原先前的扫描记录及书签备份,还原后备份项目会与现有记录合并。备份档案该以ISQBK作为档名结尾。", + "RESTORE_FAILED": "还原失败", + "RESTORE_SECRET": "请输入密码以还原备份", + "RESTORE_SUCCESSFUL": "还原成功", + "RESTORE_WRONG_SECRET": "密码错误", + "SAVED_CONTACT": "已新增联络人", + "SAVING_CONTACT": "正在新增联络人", + "SCAN_QR_FROM_IMAGE": "简易 QR 使用「cozmo/jsQR」模组扫描图片里 QR 码。请注意,", + "SCAN_QR_FROM_IMAGE_R1": "导入图片时您必须授权程序存取储存空间或相簿。", + "SCAN_QR_FROM_IMAGE_R2": "请确保所选择的图片并非透明背景。建议选择非相机摄取的图片。", + "SCAN_QR_FROM_IMAGE_R3": "若要扫描整张图片,请取消或略过相片编辑步骤。(如适用)", + "SEARCH": "您想搜索哪一个内容?", + "SEARCH_ENGINE_EXPLAIN": "设定搜索引擎,以在结果页面使用此引擎进行关键字搜索。", + "SHARE_QR": "扫一扫!\n\n从「简易 QR」分享", + "SMS_MAX_LENGTH": "最多 160 个字元", + "SSID_MAX_LENGTH": "最多 32 个字元", + "START_PAGE_EXPLAIN": "选择应用程序的起始页面。", + "START_PAGE_HEADER_EXPLAIN": "在起始页显示「简易 QR」顶栏。", + "TAG_MAX_LENGTH": "最多 30 个字元", + "TAG_MAX_LENGTH_EXPLAIN": "标签长度不能多于 30 个字元。", + "TUTORIAL_NOT_SHOW_AGAIN": "下次不再提醒", + "TUTORIAL_SWIPE_LEFT": "向左划项目可删除相关记录", + "TUTORIAL_SWIPE_RIGHT": "向右划项目可将相关记录的文字加入书签 / 修改标签", + "UNDO_DELETE": "您可在数秒内复原记录", + "VIBRATION_EXPLAIN": "提供震动或触感反馈。请注意,并非所有设备皆支援此功能。" }, "BARCODE_TYPE": { - "UPC": "通用产品代码", - "EAN": "欧洲商品条码", "1D": "一维条码", "AZTEC": "阿兹特克码", "DATA_MATRIX": "数据矩阵", - "MAXICODE": "MaxiCode 条码", - "PDF_417": "PDF417 条码", + "EAN": "欧洲商品条码", + "MAXICODE": "MaxiCode", + "PDF_417": "PDF417", "QR_CODE": "QR 码", - "RSS": "GS1 DataBar 条码" + "RSS": "GS1 DataBar", + "UPC": "通用产品代码" }, "UPDATE": { - "UPDATE_NOTES_ANDROID": "
  • 界面更新
  • 改善效能及修正已知问题
", - "UPDATE_NOTES_IOS": "
  • 解决不时弹出「版本内容」的问题
  • 解决新增联络人的权限问题
" + "UPDATE_NOTES_ANDROID": "

此版本为您带来多项更新和新功能,详情请查看 GitHub。

", + "UPDATE_NOTES_IOS": "

此版本为您带来多项更新和新功能,详情请查看 GitHub。

" } } \ No newline at end of file diff --git a/src/assets/i18n/zh-HK.json b/src/assets/i18n/zh-HK.json index a459764..f741259 100644 --- a/src/assets/i18n/zh-HK.json +++ b/src/assets/i18n/zh-HK.json @@ -1,275 +1,322 @@ { - "SIMPLE_QR": "簡易QR", - "MESSAGE": "信息", - "UPDATE_NOTES": "版本內容", - "PERMISSION_REQUIRED": "需要授權", - "CAMERA_PAUSED": "掃描已暫停", - "CREATE_QRCODE": "建立 QR 碼", - "CREATE_QRCODE_MAX_LENGTH": "最多 1817 個字元", - "QRCODE_CONTENT": "QR 碼內容", - "CONTENT": "内容", - "EXIT_APP": "離開應用程式", - "WEBSITE": "網站", - "PHONE_CALL": "通話", - "SEARCH": "搜尋", - "COPY": "複製", - "OPEN": "開啟", - "YES": "是", - "NO": "否", - "OK": "好的", + "100_RECORDS": "100 個記錄", + "30_RECORDS": "30 個記錄", + "49_DIGIT": "49 位", + "50_RECORDS": "50 個記錄", "ABOUT": "關於", - "ENTER": "輸入", - "CONFIRM": "確定", - "CANCEL": "取消", - "RESUME": "恢復", - "ORIGINAL": "原始", - "BASE64": "Base64", - "BASE64_ENCODED": "Base64 編碼", - "BASE64_DECODED": "Base64 解碼", - "HISTORY": "歷史", - "TUTORIAL": "教學", - "TUTORIAL_TOUCH_HISTORY": "輕觸此圖示可查看您過往的掃描記錄", - "TUTORIAL_TOUCH_BOOKMARK": "輕觸此圖示可查看您收藏的書籤", - "TUTORIAL_SWIPE_LEFT": "向左劃項目可刪除相關記錄", - "TUTORIAL_SWIPE_RIGHT": "向右劃項目可將相關記錄的文字加入書籤", - "TUTORIAL_NOT_SHOW_AGAIN": "下次不再提醒", - "BOOKMARKS": "書籤", - "SCANNING_HISTORY_SHOWN": "您的掃描記錄會在這裡顯示", - "FAVOURITE_TEXT_SHOWN": "已收藏的書籤會在這裡顯示", - "REMOVE_ALL": "刪除全部", - "RESULT": "結果", - "UNDO": "還原", - "PHONE_NUMBER": "電話號碼", - "MESSAGE_CONTENT": "信息內容", - "EMAIL_TO": "電郵至", - "CC": "副本", - "BCC": "密件副本", - "EMAIL_SUBJECT": "電郵主題", - "EMAIL_BODY": "電郵內容", - "CONTACT_NAME": "聯絡人名稱", - "BARCODE_CONTENT": "條碼內容", - "BARCODE_FORMAT": "條碼格式", - "BROWSE_WEBSITE": "瀏覽網站", + "ABOUT_SIMPLE_QR": "關於簡易QR", + "ADD": "新增", + "ADD_BCC": "新增密件副本", + "ADD_CC": "新增副本", "ADD_CONTACT": "新增聯絡人", + "ADD_RECIPIENT": "新增收件者", + "APP": "應用程式", + "APPEARANCE_AND_EFFECTS": "外觀與效果", + "APP_INITIAL_PAGE": "程式起始頁面", + "APP_VERSION": "程式版本", + "AT": "於", + "AT_LEAST_1_MINUTE_LATER": "最早 1 分鐘後", + "AT_LEAST_3_MINUTES_LATER": "最早 3 分鐘後", + "AT_LEAST_5_MINUTES_LATER": "最早 5 分鐘後", + "AUTO_KILL_BACKGROUND": "自動停止背景執行", + "AUTO_LOGGING": "自動記錄", + "AUTO_MAX_BRIGHTNESS": "自動最大亮度", + "AUTO_OPEN_URL": "自動打開網址", + "AUTO_QR_CODE_POPUP": "自動彈出 QR 碼", + "BACKGROUND_COLOR": "背景顏色", + "BACKING_UP": "備份中", + "BACKUP": "備份", + "BASE64": "Base64", + "BASE64_DECODED": "Base64 解碼", + "BASE64_ENCODED": "Base64 編碼", + "BCC": "密件副本", + "BLACK": "黑色", + "BOOKMARK": "書籤", + "BOOKMARKED": "已書籤", + "BOOKMARKED_TEXTS": "已收藏的書籤會在這裡顯示", + "BOOKMARKS": "書籤", + "BRAVE_SEARCH": "Brave Search", + "BROWSE": "瀏覽", + "BROWSER": "瀏覽器", + "BROWSE_WEBSITE": "瀏覽網站", "CALL": "致電", - "SEND_MESSAGE": "傳送信息", - "SEND_EMAIL": "傳送電郵", - "CONNECT_WIFI": "連接 WiFi 網絡", - "WIFI_NETWORK": "WiFi 網絡", - "CONNECT": "連接", - "WIFI_SSID": "WiFi 名稱", - "WIFI_ENCRYPTION": "安全性", - "WIFI_HIDDEN": "隱藏的網絡?", - "NOT_AVAILABLE": "沒有", - "NOT_PROVIDED": "(沒有提供)", - "WEB_SEARCH": "搜尋", + "CANCEL": "取消", + "CC": "副本", + "CITY": "城市", + "CLEAR": "清除", + "CLOSE": "關閉", + "COLOR": "顏色", + "COLOR_THEME": "顏色主題", + "COMING_SOON": "即將推出", + "CONTACT_METHOD": "聯絡方法", + "CONTACT_NAME": "聯絡人名稱", + "CONTENT": "内容", + "CONTENT_TYPE": "內容類別", + "COPIED": "已複製", + "COPY": "複製", + "COPY_SECRET_AND_SAVE_BACKUP": "複製密碼並儲存備份", "COPY_TEXT": "複製內容", - "SHARE_QRCODE": "分享 QR 碼", + "COUNTRY": "國家", + "CREATE": "建立", + "CREATED": "建立", + "CREATE_QR_CODE": "建立 QR 碼", + "DARK": "深色", + "DATE_OF_BIRTH": "出生日期", + "DEBUG_MODE": "除錯模式", + "DECODING": "解碼中", + "DECRYPTING": "解密中", + "DETAILED": "詳細說明", + "DONE": "完成", + "DUCK_DUCK_GO": "DuckDuckGo", + "ECOSIA": "Ecosia", + "EDIT": "修改", + "EMAIL_ADDRESS": "電郵地址", + "EMAIL_BODY": "電郵內容", + "EMAIL_NTT_DOCOMO": "電郵 (NTT Docomo)", + "EMAIL_RECIPIENT": "收件者", + "EMAIL_SUBJECT": "電郵主題", + "EMAIL_TO": "電郵至", + "EMAIL_W3C_STANDARD": "電郵 (W3C 標準)", + "ENCRYPTING": "加密中", + "ERROR_CORRECTION_LEVEL": "容錯等級", + "EXIT": "離開", + "EXIT_APP": "離開程式", + "EXPORT": "匯出", + "EXPORTING": "匯出中", + "EXPORT_TO_CSV": "匯出至CSV", + "FAX_NUMBER": "傳真號碼", + "FEMALE": "女性", + "FIRST_NAME": "名字", + "FOLLOW_SYSTEM_SETTINGS": "由系統控制", + "FREE_TEXT": "自由文本", + "FULL_RESET": "完整重設", + "FUNCTIONS": "功能", + "GENDER": "性別", + "GEOLOCATION": "地理位置", + "GOOGLE_SEARCH": "Google 搜尋", + "HAPTIC_FEEDBACK_ONLY": "僅觸感反饋", + "HIDDEN_NETWORK_?": "隱藏的網絡?", + "HOME_ADDRESS": "家居地址", + "HOME_PHONE_NUMBER": "家居電話號碼", + "ICON_ONLY": "僅限圖示", + "IMPORT": "匯入", + "IMPORT_FROM_CSV": "從CSV匯入", + "IMPORT_IMAGE": "匯入圖片", + "INITIAL_SEGMENT": "起始分頁", + "JOB_TITLE": "職位名稱", + "LANGUAGE": "語言", + "LAST_NAME": "姓氏", + "LATITUDE": "緯度", + "LEVEL_H": "H 等級", + "LEVEL_L": "L 等級", + "LEVEL_M": "M 等級", + "LEVEL_Q": "Q 等級", + "LIGHT": "淺色", + "LOADING_DATA": "載入中", + "LOCK_LANDSCAPE": "鎖定橫向", + "LOCK_PORTRAIT": "鎖定縱向", + "LOG": "記錄", + "LOG_BACKUP_AND_RESTORE": "記錄、備份與還原", + "LONGITUDE": "經度", + "MALE": "男性", + "MANAGE_RECORDS": "管理記錄", + "MARGIN": "邊距", + "MESSAGE": "信息", + "MESSAGE_CONTENT": "信息內容", + "MICROSOFT_BING": "Microsoft Bing", + "MOBILE_PHONE_NUMBER": "手提電話號碼", + "MORE": "更多", + "NAME": "姓名", + "NO": "否", + "NONE": "沒有", + "NOT_AVAILABLE": "沒有", + "NOT_PROVIDED": "沒有提供", + "NOT_TO_DISCLOSE": "不願透露", + "NO_LIMIT": "無上限", + "NUMBER_OF_RECORDS": "記錄數量", + "OK": "知道", + "ONLY_DELETE_DATA": "只刪除數據", + "ONLY_RESET_SETTING": "只重設設定", + "OPEN": "打開", + "OPEN_URL": "打開網址", + "OPEN_WITH_...": "用以下方式打開", + "OPTIMIZING_DATA_...": "最佳化數據...", + "ORGANIZATION": "組織", + "ORIGINAL": "原始", + "OTHERS": "其他", + "PASSWORD": "密碼", + "PATCH_NOTES": "版本內容", + "PERMISSION_REQUIRED": "需要授權", + "PERSONAL": "個人", + "PHONE_NO": "電話號碼", + "PHONE_NUMBER": "電話號碼", + "PLEASE_WAIT": "請稍候", + "POSTAL_CODE": "郵政編碼", + "PREPARING": "準備中", + "PREVIEW": "預覽", + "PRIVACY_POLICY": "私隱政策", + "QR_CODE": "QR 碼", + "QR_CODE_AND_DECODED_RESULT": "QR 碼及解碼結果", + "QR_CODE_CONTENT": "QR 碼內容", + "QR_CODE_STYLE": "QR 碼樣式", + "RATE_THE_APP": "前往商店評分", + "RECORDS_LIMIT": "記錄上限", + "REMOVE_ALL": "刪除全部", + "REMOVE_BCC": "刪除密件副本", + "REMOVE_CC": "刪除副本", + "REMOVE_RECIPIENT": "刪除收件者", + "REPORT_ISSUE": "回報問題", + "RESET_APP": "重設程式", + "RESET_DEFAULT": "還原預設值", + "RESTORE": "還原", + "RESULT": "結果", + "SAVE": "儲存", + "SAVED": "已儲存", + "SCAN": "掃描", + "SCANNED": "掃描", + "SCANNING_FEEDBACK_ONLY": "僅掃描反饋", + "SCAN_BY_CAMERA": "相機掃描", + "SCREEN_ORIENTATION": "螢幕方向", + "SEARCH": "搜尋", + "SEARCH_ENGINE": "搜尋引擎", + "SECRET": "密碼", + "SEND": "傳送", + "SEND_EMAIL": "傳送電郵", + "SEND_MESSAGE": "傳送信息", "SETTING": "設定", "SETTINGS": "設定", - "LANGUAGE": "語言", - "SYSTEM_DEFAULT": "系統預設", - "COLOR_THEME": "顏色主題", - "LIGHT": "淺色", - "DARK": "深色", - "BLACK": "黑色", - "CAMERA_PAUSE_TRIGGER": "暫停掃描觸發", - "NEVER": "永不", - "SECONDS": "秒", - "CAMERA_PAUSE_EXPLAIN": "掃描進行時,程式會檢測設備的運動。如果您的設備靜止一段時間,程式將會暫停掃描和相機預覽以節省電力。選擇「永不」可停用這功能。", - "LOGGING_BACKUP_RESTORE": "記錄、備份與還原", - "TURN_ON": "開啟", - "TURN_OFF": "關閉", - "AUTO_LOGGING": "自動記錄", - "AUTO_LOGGING_EXPLAIN": "在您掃描、建立或重新瀏覽記錄時,條碼內容會自動被記錄並儲存在本地儲存空間。您可在「歷史」頁面瀏覽。", - "BACKUP_AND_RESTORE": "備份與還原", - "BACKUP": "備份", - "RESTORE": "還原", - "BACKUP_EXPLAIN": "您可以為現有的掃描記錄及書籤進行本地備份。備份後,您會收到一組密碼。請妥善保存密碼,否則您無法還原此備份。請注意,簡易QR並不支援跨平台備份與還原。備份時,請允許簡易QR存取儲存空間。", - "RESTORE_EXPLAIN": "您可以還原先前的掃描記錄及書籤備份,還原後備份項目會與現有記錄合併。備份檔案該以TFSQBK作為檔名結尾。", - "RESTORE_EXPLAIN_IOS": "您可以還原先前的掃描記錄及書籤備份,還原後備份項目會與現有記錄合併。備份檔案該以ISQBK作為檔名結尾。", - "COPY_AND_BACKUP": "複製並開始備份", - "ENCRYPTING": "加密中...", - "BACKING_UP": "備份中...", - "SECRET": "密碼", - "LENGTH_49": "49 位", - "LENGTH_32": "32 位", - "LENGTH_16": "16 位", - "COPY_SECRET": "只複製密碼", - "COPY_SECRET_SHARE": "複製密碼並儲存備份", - "TURN_ON_HAPTIC": "僅觸感反饋", - "TURN_ON_SCANNED": "僅掃描反饋", - "SEARCH_ENGINE": "搜尋引擎", - "GOOGLE": "Google 搜尋", - "BING": "Microsoft Bing", - "YAHOO": "Yahoo! 雅虎搜尋", - "DUCK_DUCK_GO": "DuckDuckGo", - "SEARCG_ENGINE_EXPLAIN": "設定搜尋引擎,以便在結果頁面進行搜尋。", - "ABOUT_SIMPLE_QR": "關於簡易QR", - "CHECK_GOOGLE_PLAY": "查看 Google Play", - "CHECK_APP_STORE": "查看 App Store", - "PRIVACY_POLICY": "私隱政策", - "SUPPORT_DEVELOPER": "支持開發人員", - "WATCH_ADS": "觀看廣告", - "THANKS_SUPPORT": "廣告載入中,感謝支持", - "DONATE_PAYPAL": "透過 PayPal 捐款", - "SEE_MORE": "查看更多", - "SUCCESS": "成功", - "RESTORED": "已恢復", - "SUPPORT_DEV_EXPLAIN": "簡易 QR 是一個免費的開源應用程式,歡迎您查閱源代碼及作出貢獻。", - "DEVELOPING": "開發中", - "REPORT_ISSUE": "回報問題", - "RESET_APP": "重設應用程式", - "APP_VERSION": "程式版本", - "NEW_CONTACT": "新聯絡人", - "SAVE": "儲存", - "REQUIRED": "必需", - "NEW_CONTACT_TIP": "只有名稱、電話號碼及電郵地址會被儲存", - "FIRST_NAME": "名字", - "LAST_NAME": "姓氏", - "PHONE_TAG": "電話標籤", - "EMAIL_ADDRESS": "電郵地址", - "EMAIL_TAG": "電郵標籤", - "DEFAULT_OR_OTHER": "預設 / 其他", - "MOBILE": "手提", - "HOME": "家居", - "WORK": "工作", - "PREPARING": "準備中", - "PLEASE_WAIT": "請稍候...", - "DECODING": "解碼中...", - "CHECK_PERMISSION": "檢查權限", - "CHECK_WIFI": "檢查 WiFi 開關", - "RECOGNIZE_NETWORK": "識別中", - "CONNECTING_NETWORK": "連接中", - "CONTENT_TYPE": "內容類別", - "FREE_TEXT": "任意文字", - "URL": "網址", - "VCARD_CONTACT": "vCard 聯絡人", - "PHONE": "電話號碼", - "SMS": "信息", - "EMAIL": "電郵", - "WIFI": "WiFi", - "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "建立的 QR 碼內容長度不能多於 1817 個字元", - "CREATE": "建立", - "CLEAR": "清除", - "EMAIL_RECIPIENT": "收件者", - "EMAIL_MAX_LENGTH": "最多 254 個字元", - "ADD_RECIPIENT": "新增收件者", - "REMOVE_RECIPIENT": "刪除收件者", - "ADD_CC": "新增副本", - "REMOVE_CC": "刪除副本", - "ADD_BCC": "新增密件副本", - "REMOVE_BCC": "刪除密件副本", - "EMAIL_SUBJECT_MAX_LENGTH": "最多 78 個字元", - "SMS_MAX_LENGTH": "最多 160 個字元", - "NOT_TO_DISCLOSE": "不願透露", - "MALE": "男性", - "FEMALE": "女性", - "NAME": "姓名", - "CONTACT_METHOD": "聯絡方法", - "MOBILE_PHONE_NUMBER": "手提電話號碼", - "HOME_PHONE_NUMBER": "家居電話號碼", - "WORK_PHONE_NUMBER": "工作聯絡號碼", - "FAX_NUMBER": "傳真號碼", - "ORGANIZATION": "組織", - "JOB_TITLE": "職位名稱", - "HOME_ADDRESS": "家居地址", - "STREET": "街道", - "CITY": "城市", + "SHARE": "分享", + "SHARING": "正在分享", + "SHOW": "顯示", + "SHOW_NUMBER_OR_RECORDS": "顯示記錄數量", + "SHOW_QR_CODE": "顯示 QR 碼", + "SIMPLE_QR": "簡易QR", "STATE": "州", - "POSTAL_CODE": "郵政編碼", - "COUNTRY": "國家", - "PERSONAL": "個人", - "BIRTHDAY": "出生日期", - "GENDER": "性別", - "SSID_MAX_LENGTH": "最多 32 個字元", - "NONE": "沒有", - "PASSWORD": "密碼", - "SCAN": "掃描", - "IMPORT": "匯入", - "IMPORT_IMAGE": "匯入圖片", - "CHECK_MODULE": "查閱模組", - "VIEW_SOURCE": "查閱源代碼", - "TURN_ON_TORCH": "開啟電筒", - "TURN_OFF_TORCH": "關閉電筒", - "VIBRATION": "震動效果", - "CLOSE": "關閉", + "STREET": "街道", + "SUCCESS": "成功", "SUPPORTED_BARCODE_TYPE": "支援的條碼類別", "SUPPORTED_TYPE": "支援的類別", - "DEBUG_MODE": "除錯模式", + "SYSTEM_DEFAULT": "系統預設", + "TASK": "行動", + "TASK_BUTTON_LAYOUT": "行動按鍵佈局", + "TURN_OFF": "關閉", + "TURN_ON": "開啟", + "TURNED_OFF": "已關閉", + "TURNED_ON": "已開啟", + "TUTORIAL": "教學", + "UNDO": "還原", + "UNKNOWN": "未知", + "UPDATE_SUCCESSFULLY": "更新成功", + "URL": "網址", + "VCARD_CONTACT": "vCard 聯絡人", + "VERSION_VERSION": "版本 {version}", + "VIBRATION": "震動效果", + "VIEWED": "查看", + "VIEW_BOOKMARK": "查看書籤", + "VIEW_GITHUB": "查看 GitHub", + "VIEW_LOG": "查看記錄", + "VIEW_STORE_AND_SOURCE_CODE": "查看商店及源代碼", + "VIEW_INSTRUCTIONS": "查看說明", + "WEBSITE": "網站", + "WIFI": "WiFi", + "WIFI_ENCRYPTION": "安全性", + "WIFI_SSID": "WiFi 名稱", + "WORK": "工作", + "WORK_PHONE_NUMBER": "工作聯絡號碼", + "YAHOO_SEARCH": "Yahoo! 雅虎搜尋", + "YANDEX": "Yandex", + "YES": "是", "MSG": { - "CALL_PHONE": "確定要致電 {phoneNumber} ?", - "FAIL_CALL_PHONE": "無法開啟電話", - "PREPARE_SMS": "準備信息中", - "FAIL_PREPARE_SMS": "無法傳送信息", - "SEARCH": "您想搜尋哪一個內容?", - "COPY_TEXT": "您想複製哪一個內容?", - "COPIED": "已複製", - "DECODED": "已解碼", - "NOT_BASE64_EN_DE": "內容不能被 Base64 編碼及解碼", - "NOT_BASE64_EN": "內容不能被 Base64 編碼", - "NOT_BASE64_DE": "內容不能被 Base64 解碼", - "SHARE_QR": "掃一掃!\n\n從「簡易 QR」分享", - "SAVED": "已儲存", - "EXIT_APP": "確定要離開?", - "CAMERA_PERMISSION_1": "按「設定」以授權「簡易QR」使用相機。", - "CAMERA_PERMISSION_2": "使用掃描功能,您必須授權「簡易QR」使用相機。", - "READ_IMAGE_PERMISSION": "要掃描圖片,您必須授權「簡易QR」存取儲存空間。", - "CAMERA_PAUSED": "為節省電力已暫停掃描,提起裝置或按「恢復」可繼續使用。

在「設定」可調校此功能。", - "UNDO_DELETE": "您可在數秒內還原記錄", - "RESET_APP": "確定要重設應用程式和刪除所有數據? 此動作無法被撤回。", - "REMOVE_ALL_RECORD": "確定要刪除全部記錄? 此動作無法被撤回。", - "GIVE_NAME_CONTACT": "請填寫名字", - "FAIL_START_ADS": "無法初始化廣告", - "FAIL_LOAD_ADS": "無法載入廣告", - "FAIL_SHOW_ADS": "無法顯示廣告", - "ALREADY_BOOKMARKED": "已加入書籤", - "BOOKMARKED": "成功加入書籤", - "UNBOOKMARKED": "已移除書籤", - "REMOVE_ALL_BOOKMARKS": "確定要刪除所有書籤? 此動作無法被撤回。", - "WIFI_NO_SSID": "沒有提供 WiFi 名稱", - "WIFI_PERMISSION": "必須授權使用位置資訊才能連接 WiFi", - "TURN_ON_WIFI": "請開啟 WiFi 功能", - "TURN_ON_LOCATION": "請開啟位置資訊", - "WIFI_NOT_FOUND": "您的位置不在此網絡覆蓋範圍內", - "FAIL_CONNECT_WIFI": "無法連接此網絡", - "WIFI_NO_INTERNET": "此網絡無互聯網連接", - "ANDROID_WIFI_NATIVE_SETTINGS": "基於 Android 政策,我們建議您使用原生設定來掃描此 QR 碼以連接網絡。", - "QR_CODE_VALUE_NOT_EMPTY": "QR 碼不能是空值", - "NO_QR_CODE": "此圖片沒有 QR 碼!請確保圖片背景不是透明", - "IMAGE_NO_TRANSPARENT": "請確保圖片背景不是透明", - "SCAN_QR_FROM_IMAGE": "簡易 QR 使用「cozmo/jsQR」模組實現匯入圖片掃描 QR 碼的功能。請確保匯入圖片的背景並非透明,否則模組無法掃描圖片裡的 QR 碼。匯入圖片時請授權簡易 QR 存取儲存空間。", - "BARCODE_TYPE": "掃描
  • QR 碼
  • 一維條碼
  • Aztec 碼
  • 數據矩陣碼
  • PDF417 條碼
匯入圖片
  • QR 碼
建立
  • QR 碼
", - "TAP_TO_ZOOM_IN": "輕觸以放大", - "ONLY_VCARD_3_0": "只支援 vCard 3.0", - "DEBUG_MODE_ON": "成功開啟除錯模式", + "ALREADY_BOOKMARKED": "無法新增重複書籤", + "AUTO_KILL_BACKGROUND_EXPLAIN": "

為節省系統資源及減低耗電,當程式進入背景執行時,設定自動停止執行程式及所有背景活動的時間。


若選擇由系統控制,程式本身將不會干預其運作及資源佔用,全由系統控制。


請注意,在某些情況下系統可能會提前停止程式的背景運作。

", + "AUTO_LOGGING_EXPLAIN": "在您掃描、建立或查看記錄時,條碼內容會自動被記錄並儲存在本地儲存空間。您可在「記錄」頁面瀏覽所有記錄。", + "AUTO_MAX_BRIGHTNESS_EXPLAIN": "當顯示 QR 碼時,自動調校螢幕亮度到最大。", + "AUTO_OPEN_URL_EXPLAIN": "掃描QR碼且內容為URL時,會自動打開該URL。", + "AUTO_SHOW_QR_EXPLAIN": "在進行以下已選擇的動作後,自動彈出 QR 碼。", + "BACKUP_EXPLAIN": "您可以為現有的掃描記錄及書籤進行本地備份。備份後,您會得到一組密碼。請妥善保存密碼,否則您無法還原此備份。請注意,簡易QR並不支援跨平台備份與還原。", "BACKUP_FAILED": "備份失敗", "BACKUP_FAILED_2": "備份失敗。請確保已允許「簡易QR」存取儲存空間", "BACKUP_SUCCESSFULLY": "

成功備份。請妥善保存備份檔及以下密碼

{secret}

", - "RESTORE_SECRET": "請輸入密碼以還原備份", - "INVALID_BK_FILE": "這不是有效的備份檔", - "RESTORE_FAILED": "還原失敗", - "PLEASE_INPUT_VALID_SECRET": "請輸入有效密碼", - "RESTORE_WRONG_SECRET": "密碼錯誤", - "RESTORE_SUCCESSFUL": "還原成功", + "BARCODE_TYPE": "掃描
  • QR 碼
  • 一維條碼
  • Aztec 碼
  • 數據矩陣碼
  • PDF417 條碼
匯入圖片
  • QR 碼
建立
  • QR 碼
", + "BOOKMARKED": "成功加入書籤", + "BUTTON_DISPLAY_EXPLAIN": "顯示或隱藏行動按鍵。", + "BUTTON_STYLE_EXPLAIN": "選擇行動按鍵的樣式。", + "CAMERA_PERMISSION": "要使用掃描功能,您必須授權「簡易QR」使用相機。", + "CONTACT_PERMISSION": "要新增聯絡人,您必須授權「簡易QR」存取通訊錄。", "COPIED_SECRET": "已複製密碼", - "SAVED_CONTACT": "已新增聯絡人", + "COPY_TEXT": "您想複製哪一個內容?", + "CREATE_QRCODE_MAX_LENGTH": "最多 1817 個字元", + "CREATE_QRCODE_MAX_LENGTH_EXPLAIN": "建立的 QR 碼內容長度不能多於 1817 個字元。", + "DEBUG_MODE_ON": "成功開啟除錯模式", + "DELETE_OVERFLOWED_RECORDS": "離開本頁面後,溢出的記錄會被永久刪除。", + "EMAIL_MAX_LENGTH": "最多 254 個字元", + "EMAIL_SUBJECT_MAX_LENGTH": "最多 78 個字元", + "ERROR_CORRECTION_LEVEL_EXPLAIN": "

QR 碼具有容錯能力,即使圖形有破損,仍然可以被機器讀取內容。


容錯率分為四個等級:

L 等級可容錯 7% 的字碼;

M 等級可容錯 15% 的字碼;

Q 等級可容錯 25% 的字碼;

H 等級可容錯 30% 的字碼。


容錯率愈高,QR 碼圖形面積則愈大。因此,在一般情況下建議使用 M 等級。

", + "EXIT_APP": "

確定要離開?

若您喜歡簡易QR,歡迎前往商店評分。

", + "EXPORT_TO_CSV_EXPLAIN": "您可以將所有掃描記錄及書籤匯出至CSV檔案。", "FAILED_SAVING_CONTACT": "無法新增聯絡人", - "CONTACT_PERMISSION": "要新增聯絡人,您必須授權「簡易QR」存取通訊錄。" + "FAIL_PREPARE_SMS": "無法傳送信息", + "IMPORT_FAILED": "匯入失敗", + "IMPORT_FROM_CSV_EXPLAIN": "您可以從簡易QR定義的CSV檔案匯入記錄和書籤。若您想在Android和iOS設備之間傳輸數據,請使用此功能。", + "INPUT_TAG": "請為此書籤輸入一個標籤", + "INVALID_BK_FILE": "這不是有效的備份檔", + "INVALID_CSV_FILE": "這不是有效的CSV檔", + "NOT_BASE64_DE": "內容不能被 Base64 解碼", + "NOT_BASE64_EN": "內容不能被 Base64 編碼", + "NOT_BASE64_EN_DE": "內容不能被 Base64 編碼及解碼", + "NO_QR_CODE": "此圖片沒有 QR 碼", + "ONLY_VCARD_3_0": "只支援 vCard 3.0", + "PLEASE_INPUT_VALID_SECRET": "請輸入有效密碼", + "PORTRAIT_ONLY": "已鎖定縱向", + "PREPARE_SMS": "準備信息中", + "PREVIOUS_RECORDS": "過往掃描、建立或查看的記錄", + "QR_CODE_VALUE_NOT_EMPTY": "QR 碼不能是空值", + "READ_IMAGE_PERMISSION": "要掃描圖片,您必須授權「簡易QR」存取儲存空間。", + "RECORDS_LIMIT_EXPLAIN": "限制記錄的數量,超過上限時舊記錄會被刪除。", + "REMOVE_ALL_BOOKMARKS": "確定要刪除所有書籤? 此動作無法被撤回。", + "REMOVE_ALL_RECORD": "確定要刪除全部記錄? 此動作無法被撤回。", + "RESET_APP": "確定要重設應用程式和刪除所有數據? 此動作無法被撤回。", + "RESET_DEFAULT": "確定要還原預設值?", + "RESTORE_EXPLAIN": "您可以還原先前的掃描記錄及書籤備份,還原後備份項目會與現有記錄合併。備份檔案該以TFSQBK作為檔名結尾。", + "RESTORE_EXPLAIN_IOS": "您可以還原先前的掃描記錄及書籤備份,還原後備份項目會與現有記錄合併。備份檔案該以ISQBK作為檔名結尾。", + "RESTORE_FAILED": "還原失敗", + "RESTORE_SECRET": "請輸入密碼以還原備份", + "RESTORE_SUCCESSFUL": "還原成功", + "RESTORE_WRONG_SECRET": "密碼錯誤", + "SAVED_CONTACT": "已新增聯絡人", + "SAVING_CONTACT": "正在新增聯絡人", + "SCAN_QR_FROM_IMAGE": "簡易 QR 使用「cozmo/jsQR」模組掃描圖片裡 QR 碼。請注意,", + "SCAN_QR_FROM_IMAGE_R1": "匯入圖片時您必須授權程式存取儲存空間或相簿。", + "SCAN_QR_FROM_IMAGE_R2": "請確保所選擇的圖片並非透明背景。建議選擇非相機攝取的圖片。", + "SCAN_QR_FROM_IMAGE_R3": "若要掃描整張圖片,請取消或略過相片編輯步驟。(如適用)", + "SEARCH": "您想搜尋哪一個內容?", + "SEARCH_ENGINE_EXPLAIN": "設定搜尋引擎,以在結果頁面使用此引擎進行關鍵字搜尋。", + "SHARE_QR": "掃一掃!\n\n從「簡易 QR」分享", + "SMS_MAX_LENGTH": "最多 160 個字元", + "SSID_MAX_LENGTH": "最多 32 個字元", + "START_PAGE_EXPLAIN": "選擇應用程式的起始頁面。", + "START_PAGE_HEADER_EXPLAIN": "在起始頁顯示「簡易 QR」頂欄。", + "TAG_MAX_LENGTH": "最多 30 個字元", + "TAG_MAX_LENGTH_EXPLAIN": "標籤長度不能多於 30 個字元。", + "TUTORIAL_NOT_SHOW_AGAIN": "下次不再提醒", + "TUTORIAL_SWIPE_LEFT": "向左劃項目可刪除相關記錄", + "TUTORIAL_SWIPE_RIGHT": "向右劃項目可將相關記錄的文字加入書籤 / 修改標籤", + "UNDO_DELETE": "您可在數秒內還原記錄", + "VIBRATION_EXPLAIN": "提供震動或觸感反饋。請注意,並非所有設備皆支援此功能。" }, "BARCODE_TYPE": { - "UPC": "通用產品代碼", - "EAN": "歐洲商品條碼", "1D": "一維條碼", "AZTEC": "阿茲特克碼", "DATA_MATRIX": "數據矩陣", - "MAXICODE": "MaxiCode 條碼", - "PDF_417": "PDF417 條碼", + "EAN": "歐洲商品條碼", + "MAXICODE": "MaxiCode", + "PDF_417": "PDF417", "QR_CODE": "QR 碼", - "RSS": "GS1 DataBar 條碼" + "RSS": "GS1 DataBar", + "UPC": "通用產品代碼" }, "UPDATE": { - "UPDATE_NOTES_ANDROID": "
  • 界面更新
  • 改善效能及修正已知問題
", - "UPDATE_NOTES_IOS": "
  • 解決不時彈出「版本內容」的問題
  • 解決新增聯絡人的權限問題
" + "UPDATE_NOTES_ANDROID": "

此版本為您帶來多項更新和新功能,詳情請查看 GitHub。

", + "UPDATE_NOTES_IOS": "

此版本為您帶來多項更新和新功能,詳情請查看 GitHub。

" } } \ No newline at end of file diff --git a/src/assets/icon/ads.svg b/src/assets/icon/ads.svg deleted file mode 100644 index d5a288d..0000000 --- a/src/assets/icon/ads.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/assets/icon/book.svg b/src/assets/icon/book.svg deleted file mode 100644 index b12416f..0000000 --- a/src/assets/icon/book.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/icon/brave-search.svg b/src/assets/icon/brave-search.svg new file mode 100644 index 0000000..9edb6fd --- /dev/null +++ b/src/assets/icon/brave-search.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/assets/icon/default.svg b/src/assets/icon/default.svg deleted file mode 100644 index caa1433..0000000 --- a/src/assets/icon/default.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/assets/icon/dollar.svg b/src/assets/icon/dollar.svg deleted file mode 100644 index d285291..0000000 --- a/src/assets/icon/dollar.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/icon/duck-duck-go.svg b/src/assets/icon/duck-duck-go.svg index 5fa4b67..eb17b25 100644 --- a/src/assets/icon/duck-duck-go.svg +++ b/src/assets/icon/duck-duck-go.svg @@ -1,5 +1,4 @@ - \ No newline at end of file diff --git a/src/assets/icon/ecosia.svg b/src/assets/icon/ecosia.svg new file mode 100644 index 0000000..d76bc36 --- /dev/null +++ b/src/assets/icon/ecosia.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/icon/number1.svg b/src/assets/icon/number1.svg new file mode 100644 index 0000000..741039e --- /dev/null +++ b/src/assets/icon/number1.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/assets/icon/number2.svg b/src/assets/icon/number2.svg new file mode 100644 index 0000000..a911fc4 --- /dev/null +++ b/src/assets/icon/number2.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/src/assets/icon/number3.svg b/src/assets/icon/number3.svg new file mode 100644 index 0000000..506b894 --- /dev/null +++ b/src/assets/icon/number3.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/src/assets/icon/pearl-cup.svg b/src/assets/icon/pearl-cup.svg deleted file mode 100644 index 4fe46f7..0000000 --- a/src/assets/icon/pearl-cup.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/assets/icon/popup.svg b/src/assets/icon/popup.svg new file mode 100644 index 0000000..1ef5a5f --- /dev/null +++ b/src/assets/icon/popup.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/assets/icon/premium-cup.svg b/src/assets/icon/premium-cup.svg deleted file mode 100644 index ecfd970..0000000 --- a/src/assets/icon/premium-cup.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/icon/qr-code.svg b/src/assets/icon/qr-code.svg deleted file mode 100644 index 0fff8e0..0000000 --- a/src/assets/icon/qr-code.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/assets/icon/screen-rotation.svg b/src/assets/icon/screen-rotation.svg new file mode 100644 index 0000000..7aef6a9 --- /dev/null +++ b/src/assets/icon/screen-rotation.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/assets/icon/search-web.svg b/src/assets/icon/search-web.svg deleted file mode 100644 index 3fb0e9d..0000000 --- a/src/assets/icon/search-web.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/assets/icon/touch.svg b/src/assets/icon/touch.svg new file mode 100644 index 0000000..8935be1 --- /dev/null +++ b/src/assets/icon/touch.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/assets/icon/vibration.svg b/src/assets/icon/vibration.svg index 006379d..3e43f26 100644 --- a/src/assets/icon/vibration.svg +++ b/src/assets/icon/vibration.svg @@ -1,5 +1,7 @@ - + + - - + + \ No newline at end of file diff --git a/src/assets/icon/yandex.svg b/src/assets/icon/yandex.svg new file mode 100644 index 0000000..6e818f7 --- /dev/null +++ b/src/assets/icon/yandex.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/assets/shapes.svg b/src/assets/shapes.svg deleted file mode 100644 index d370b4d..0000000 --- a/src/assets/shapes.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 989a260..0db0890 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,7 +1,5 @@ export const environment = { production: true, storageScanRecordKey: "RZUeHwaYWGkiNhsb5nld7vdDYE7pzRyB", - storageBookmarkKey: "lB9STlXHpk7G8STLcJZNreiIxeFWPxPS", - storageTopBookmarkKey: "hsuSbyV9o8I6NT0sh3MiVtKhwK6i6UWQ", - paypalDonateUrl: "https://github.com/tomfong/simple-qr" + storageBookmarkKey: "lB9STlXHpk7G8STLcJZNreiIxeFWPxPS" }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index c3dda79..580a9fb 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,7 +1,5 @@ export const environment = { production: false, storageScanRecordKey: "RZUeHwaYWGkiNhsb5nld7vdDYE7pzRyB", - storageBookmarkKey: "lB9STlXHpk7G8STLcJZNreiIxeFWPxPS", - storageTopBookmarkKey: "hsuSbyV9o8I6NT0sh3MiVtKhwK6i6UWQ", - paypalDonateUrl: "https://github.com/tomfong/simple-qr" + storageBookmarkKey: "lB9STlXHpk7G8STLcJZNreiIxeFWPxPS" }; diff --git a/src/global.scss b/src/global.scss index 381233d..d7b5b44 100644 --- a/src/global.scss +++ b/src/global.scss @@ -51,33 +51,6 @@ overflow-wrap: anywhere; } -.modal-page .modal-wrapper { - max-height: 70%; - position: absolute; - display: block; - max-width: 90%; - border-radius: 15px; - --background: transparent; -} - -.tutorial-modal-page .modal-wrapper { - max-height: 70%; - position: absolute; - display: block; - max-width: 80%; - border-radius: 5px; - --background: transparent; -} - -.qrcode-modal .modal-wrapper { - max-height: 350px; - position: absolute; - display: block; - max-width: 90%; - border-radius: 5px; - --background: transparent !important; -} - .toolbar-start-btn { padding-left: 10px; } @@ -87,6 +60,14 @@ } .fullscreen-vertical-center { + display: flex; + flex-direction: column; + // align-items: center; + place-content: center; + min-height: 100%; +} + +.vertical-center { display: flex; flex-direction: column; align-items: center; @@ -128,6 +109,33 @@ ion-tab-button:not(.tab-selected)::part(native):hover { opacity: 0.5; } +.alert-input-no-border .alert-checkbox-group { + border: 0px !important; +} + +.alert-input-no-border .alert-checkbox-label { + overflow: auto !important; + white-space: pre-line !important; +} + .alert-can-copy { user-select: text !important; +} + +.alert-button.sc-ion-alert-ios { + border-left: 0 !important; + border-right: 0 !important; +} + +.fullscreen-modal { + --min-width: 100%; +} + +.pre-line { + white-space: pre-line !important; +} + +.mat-icon { + height: 28px !important; + width: 28px !important; } \ No newline at end of file diff --git a/src/index.html b/src/index.html index 715fd8a..4129ec7 100644 --- a/src/index.html +++ b/src/index.html @@ -17,9 +17,6 @@ - - - diff --git a/src/test.ts b/src/test.ts index 4bf4afb..ae25f27 100644 --- a/src/test.ts +++ b/src/test.ts @@ -7,13 +7,6 @@ import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -declare const require: { - context(path: string, deep?: boolean, filter?: RegExp): { - keys(): string[]; - (id: string): T; - }; -}; - // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, @@ -21,7 +14,3 @@ getTestBed().initTestEnvironment( teardown: { destroyAfterEach: false } } ); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); diff --git a/src/theme/variables.scss b/src/theme/variables.scss index e95b324..559f75f 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -1,78 +1,24 @@ -@use '@angular/material' as mat; +@use "@angular/material" as mat; @import "@angular/material/theming"; +// TODO(v15): As of v15 mat.legacy-core no longer includes default typography styles. +// The following line adds: +// 1. Default typography styles for all components +// 2. Styles for typography hierarchy classes (e.g. .mat-headline-1) +// If you specify typography styles for the components you use elsewhere, you should delete this line. +// If you don't need the default component typographies but still want the hierarchy styles, +// you can delete this line and instead use: +// `@include mat.legacy-typography-hierarchy(mat.define-legacy-typography-config());` +@include mat.legacy-typography-hierarchy(mat.define-legacy-typography-config()); +@include mat.legacy-core(); @include mat.core(); -$custom-primary: ( - 50: #00a5aa, - 100: #00a5aa, - 200: #00a5aa, - 300: #00a5aa, - 400: #00a5aa, - 500: #00a5aa, - 600: #00a5aa, - 700: #00a5aa, - 800: #00a5aa, - 900: #00a5aa, - A100: #00a5aa, - A200: #00a5aa, - A400: #00a5aa, - A700: #00a5aa, - contrast: ( - 50: white, - 100: white, - 200: white, - 300: white, - 400: white, - 500: white, - 600: white, - 700: white, - 800: white, - 900: white, - A100: white, - A200: white, - A400: white, - A700: white, - ), -); -$custom-secondary: ( - 50: #00a5aa, - 100: #00a5aa, - 200: #00a5aa, - 300: #00a5aa, - 400: #00a5aa, - 500: #00a5aa, - 600: #00a5aa, - 700: #00a5aa, - 800: #00a5aa, - 900: #00a5aa, - A100: #00a5aa, - A200: #00a5aa, - A400: #00a5aa, - A700: #00a5aa, - contrast: ( - 50: white, - 100: white, - 200: white, - 300: white, - 400: white, - 500: white, - 600: white, - 700: white, - 800: white, - 900: white, - A100: white, - A200: white, - A400: white, - A700: white, - ), -); -$app-primary: mat.define-palette($custom-primary); -$app-accent: mat.define-palette($custom-secondary); + +$app-primary: mat.define-palette(mat.$cyan-palette, 600); $app-warn: mat.define-palette(mat.$red-palette); $app-light-theme: mat.define-light-theme( ( color: ( primary: $app-primary, - accent: $app-accent, + accent: $app-primary, warn: $app-warn, ), ) @@ -80,7 +26,7 @@ $app-light-theme: mat.define-light-theme( $app-dark-theme: mat.define-dark-theme( ( color: ( - primary: $app-accent, + primary: $app-primary, accent: $app-primary, warn: $app-warn, ), @@ -89,10 +35,10 @@ $app-dark-theme: mat.define-dark-theme( $app-black-theme: mat.define-dark-theme( ( color: ( - primary: $app-accent, + primary: $app-primary, accent: $app-primary, warn: $app-warn, - ), + ) ) ); @@ -106,12 +52,15 @@ $app-black-theme: mat.define-dark-theme( .ng-mat-black { @include mat.all-component-themes($app-black-theme); + .mdc-menu-surface { + background-color: #0f0f0f !important; + } } :root { /** primary **/ --ion-color-primary: #00a5aa; - --ion-color-primary-rgb: 0, 165, 170; + --ion-color-primary-rgb: 0, 172, 193; --ion-color-primary-contrast: #ffffff; --ion-color-primary-contrast-rgb: 255, 255, 255; --ion-color-primary-shade: #00a5aa; @@ -119,7 +68,7 @@ $app-black-theme: mat.define-dark-theme( /** secondary **/ --ion-color-secondary: #00a5aa; - --ion-color-secondary-rgb: 0, 165, 170; + --ion-color-secondary-rgb: 0, 172, 193; --ion-color-secondary-contrast: #ffffff; --ion-color-secondary-contrast-rgb: 255, 255, 255; --ion-color-secondary-shade: #00a5aa; @@ -165,6 +114,14 @@ $app-black-theme: mat.define-dark-theme( --ion-color-dark-shade: #1e2023; --ion-color-dark-tint: #383a3e; + /** darker **/ + --ion-color-darker: #1a1a1a; + --ion-color-darker-rgb: 26, 26, 26; + --ion-color-darker-contrast: #ffffff; + --ion-color-darker-contrast-rgb: 255, 255, 255; + --ion-color-darker-shade: #1a1a1a; + --ion-color-darker-tint: #1a1a1a; + /** medium **/ --ion-color-medium: #92949c; --ion-color-medium-rgb: 146, 148, 156; @@ -244,11 +201,35 @@ $app-black-theme: mat.define-dark-theme( --ion-color-metal-contrast-rgb: 212, 175, 55; --ion-color-metal-shade: #996515; --ion-color-metal-tint: #996515; + + /** red **/ + --ion-color-red: #ff0000; + --ion-color-red-rgb: 255, 0, 0; + --ion-color-red-contrast: #ffffff; + --ion-color-red-contrast-rgb: 255, 255, 255; + --ion-color-red-shade: #ff0000; + --ion-color-red-tint: #ff0000; + + /** green **/ + --ion-color-green: #00ff00; + --ion-color-green-rgb: 0, 255, 0; + --ion-color-green-contrast: #ffffff; + --ion-color-green-contrast-rgb: 255, 255, 255; + --ion-color-green-shade: #00ff00; + --ion-color-green-tint: #00ff00; + + /** blue **/ + --ion-color-blue: #0000ff; + --ion-color-blue-rgb: 0, 0, 255; + --ion-color-blue-contrast: #ffffff; + --ion-color-blue-contrast-rgb: 255, 255, 255; + --ion-color-blue-shade: #0000ff; + --ion-color-blue-tint: #0000ff; } body.dark { --ion-color-primary: #00a5aa; - --ion-color-primary-rgb: 0, 165, 170; + --ion-color-primary-rgb: 0, 172, 193; --ion-color-primary-contrast: #ffffff; --ion-color-primary-contrast-rgb: 255, 255, 255; --ion-color-primary-shade: #00a5aa; @@ -381,6 +362,30 @@ body.dark { --ion-color-purple-contrast-rgb: 0, 0, 0; --ion-color-purple-shade: purple; --ion-color-purple-tint: purple; + + /** red **/ + --ion-color-red: #ff0000; + --ion-color-red-rgb: 255, 0, 0; + --ion-color-red-contrast: #ffffff; + --ion-color-red-contrast-rgb: 255, 255, 255; + --ion-color-red-shade: #ff0000; + --ion-color-red-tint: #ff0000; + + /** green **/ + --ion-color-green: #00ff00; + --ion-color-green-rgb: 0, 255, 0; + --ion-color-green-contrast: #ffffff; + --ion-color-green-contrast-rgb: 255, 255, 255; + --ion-color-green-shade: #00ff00; + --ion-color-green-tint: #00ff00; + + /** blue **/ + --ion-color-blue: #0000ff; + --ion-color-blue-rgb: 0, 0, 255; + --ion-color-blue-contrast: #ffffff; + --ion-color-blue-contrast-rgb: 255, 255, 255; + --ion-color-blue-shade: #0000ff; + --ion-color-blue-tint: #0000ff; } .ios body.dark { @@ -597,6 +602,15 @@ body.dark { } } +.ion-color-darker { + --ion-color-base: var(--ion-color-darker); + --ion-color-base-rgb: var(--ion-color-darker-rgb); + --ion-color-contrast: var(--ion-color-darker-contrast); + --ion-color-contrast-rgb: var(--ion-color-darker-contrast-rgb); + --ion-color-shade: var(--ion-color-darker-shade); + --ion-color-tint: var(--ion-color-darker-tint); +} + .ion-color-dimgray { --ion-color-base: var(--ion-color-dimgray); --ion-color-base-rgb: var(--ion-color-dimgray-rgb); @@ -678,6 +692,33 @@ body.dark { --ion-color-tint: var(--ion-color-purple-tint); } +.ion-color-red { + --ion-color-base: var(--ion-color-red); + --ion-color-base-rgb: var(--ion-color-red-rgb); + --ion-color-contrast: var(--ion-color-red-contrast); + --ion-color-contrast-rgb: var(--ion-color-red-contrast-rgb); + --ion-color-shade: var(--ion-color-red-shade); + --ion-color-tint: var(--ion-color-red-tint); +} + +.ion-color-green { + --ion-color-base: var(--ion-color-green); + --ion-color-base-rgb: var(--ion-color-green-rgb); + --ion-color-contrast: var(--ion-color-green-contrast); + --ion-color-contrast-rgb: var(--ion-color-green-contrast-rgb); + --ion-color-shade: var(--ion-color-green-shade); + --ion-color-tint: var(--ion-color-green-tint); +} + +.ion-color-blue { + --ion-color-base: var(--ion-color-blue); + --ion-color-base-rgb: var(--ion-color-blue-rgb); + --ion-color-contrast: var(--ion-color-blue-contrast); + --ion-color-contrast-rgb: var(--ion-color-blue-contrast-rgb); + --ion-color-shade: var(--ion-color-blue-shade); + --ion-color-tint: var(--ion-color-blue-tint); +} + html, body { height: 100%; @@ -688,4 +729,4 @@ body { } /* Importing Bootstrap SCSS file. */ -@import "~bootstrap/scss/bootstrap"; +@import "bootstrap/scss/bootstrap"; diff --git a/tsconfig.app.json b/tsconfig.app.json index 82d91dc..b120c1d 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -3,7 +3,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": ["node"] }, "files": [ "src/main.ts", diff --git a/tsconfig.json b/tsconfig.json index 26fa1e1..77028e5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,9 +10,13 @@ "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, - "target": "es5", + "target": "ES2022", "module": "es2020", - "lib": ["es2018", "dom"] + "lib": [ + "es2018", + "dom" + ], + "useDefineForClassFields": false }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false,