🎉 Begin a project
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log/OS Files
|
||||
*.log
|
||||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.apk
|
||||
output.json
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
661
LICENSE
Normal file
@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are 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.
|
||||
|
||||
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.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
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 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 work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero 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 Affero 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 Affero 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 Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
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 AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
44
app/build.gradle
Normal file
@ -0,0 +1,44 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.paigramteam.nomihoyoapp'
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.paigramteam.nomihoyoapp"
|
||||
minSdk 24
|
||||
targetSdk 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
}
|
21
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
20
app/release/output-metadata.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.paigramteam.nomihoyoapp",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.paigramteam.nomihoyoapp;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("com.paigramteam.nomihoyoapp", appContext.getPackageName());
|
||||
}
|
||||
}
|
57
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.ByWebViewActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:hardwareAccelerated="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/WebViewTheme"
|
||||
tools:ignore="LockedOrientationActivity"
|
||||
android:exported="true">
|
||||
s
|
||||
<!--需要添加下面的intent-filter配置-->
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!--打开https开头的网页-->
|
||||
<data android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!--打开http开头的网页-->
|
||||
<data android:scheme="http" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
27
app/src/main/assets/404_error.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Error</title>
|
||||
<meta name="referrer" content="never">
|
||||
<meta name="viewport"
|
||||
content="width=device-width,
|
||||
initial-scale=1.0, minimum-scale=1.0,
|
||||
maximum-scale=1.0, user-scalable=no"/>
|
||||
<style type="text/css">
|
||||
html,body{background:#EFEFF4;}
|
||||
.d1{text-align:center;width:100%;position:absolute;left:0;top:40%;}
|
||||
.d1 span{display:inline-block;font-size:0.64rem;margin:0 0.2rem;}
|
||||
.d1 div{font-size:0.32rem;margin-top:0.2rem;}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="d1">
|
||||
<span>404</span>
|
||||
<span>Not</span>
|
||||
<span>Found</span>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
99
app/src/main/assets/WebViewJavascriptBridge.js
Normal file
@ -0,0 +1,99 @@
|
||||
;
|
||||
(function () {
|
||||
if (window.WebViewJavascriptBridge) {
|
||||
return;
|
||||
}
|
||||
var messageHandlers = {};
|
||||
var responseCallbacks = {};
|
||||
var uniqueId = 1;
|
||||
var dispatchMessagesWithTimeoutSafety = true;
|
||||
var random = 1;
|
||||
|
||||
function _doSend(message, responseCallback) {
|
||||
if (responseCallback) {
|
||||
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
|
||||
responseCallbacks[callbackId] = responseCallback;
|
||||
message['callbackId'] = callbackId;
|
||||
}
|
||||
var msg=JSON.stringify(message || {});
|
||||
if(window.WVJBInterface){
|
||||
WVJBInterface.notice(msg);
|
||||
}else{
|
||||
prompt("_wvjbxx",msg);
|
||||
}
|
||||
}
|
||||
|
||||
var bridge = {
|
||||
registerHandler: function (handlerName, handler) {
|
||||
messageHandlers[handlerName] = handler;
|
||||
},
|
||||
|
||||
callHandler: function (handlerName, data, responseCallback) {
|
||||
if (arguments.length == 2 && typeof data == 'function') {
|
||||
responseCallback = data;
|
||||
data = null;
|
||||
}
|
||||
_doSend({
|
||||
handlerName: handlerName,
|
||||
data: data
|
||||
}, responseCallback);
|
||||
},
|
||||
disableJavascriptAlertBoxSafetyTimeout: function (disable) {
|
||||
this.callHandler("_disableJavascriptAlertBoxSafetyTimeout", disable !== false)
|
||||
},
|
||||
_handleMessageFromJava: function (messageJSON) {
|
||||
_dispatchMessageFromJava(messageJSON);
|
||||
},
|
||||
hasNativeMethod: function (name, responseCallback) {
|
||||
this.callHandler('_hasNativeMethod', name, responseCallback);
|
||||
}
|
||||
};
|
||||
|
||||
bridge.registerHandler('_hasJavascriptMethod', function (data, responseCallback) {
|
||||
responseCallback(!!messageHandlers[data])
|
||||
})
|
||||
|
||||
function _dispatchMessageFromJava(message) {
|
||||
var messageHandler;
|
||||
var responseCallback;
|
||||
if (message.responseId) {
|
||||
responseCallback = responseCallbacks[message.responseId];
|
||||
if (!responseCallback) {
|
||||
return;
|
||||
}
|
||||
responseCallback(message.responseData);
|
||||
delete responseCallbacks[message.responseId];
|
||||
} else {
|
||||
if (message.callbackId) {
|
||||
var callbackResponseId = message.callbackId;
|
||||
responseCallback = function (responseData) {
|
||||
_doSend({
|
||||
handlerName: message.handlerName,
|
||||
responseId: callbackResponseId,
|
||||
responseData: responseData
|
||||
});
|
||||
};
|
||||
}
|
||||
var handler = messageHandlers[message.handlerName];
|
||||
if (!handler) {
|
||||
console.log("WebViewJavascriptBridge: WARNING: no handler for message from java", message);
|
||||
} else {
|
||||
handler(message.data, responseCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var callbacks = window.WVJBCallbacks;
|
||||
delete window.WVJBCallbacks;
|
||||
if (callbacks) {
|
||||
for (var i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i](bridge);
|
||||
}
|
||||
}
|
||||
window.WebViewJavascriptBridge = bridge;
|
||||
|
||||
window.close=function(){
|
||||
bridge.callHandler("_closePage")
|
||||
}
|
||||
|
||||
})();
|
39
app/src/main/java/com/paigramteam/nomihoyoapp/App.java
Normal file
@ -0,0 +1,39 @@
|
||||
package com.paigramteam.nomihoyoapp;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.multidex.MultiDex;
|
||||
|
||||
import com.paigramteam.nomihoyoapp.utils.WebTools;
|
||||
|
||||
/**
|
||||
* @author jingbin
|
||||
* @data 2018/2/2
|
||||
*/
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
private static App app;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
app = this;
|
||||
}
|
||||
|
||||
public static App getInstance() {
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法数超64k 解决 https://developer.android.com/studio/build/multidex?hl=zh-cn
|
||||
* 继承 MultiDexApplication 或 实现此方法。
|
||||
*/
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
WebTools.handleWebViewDir(base);
|
||||
MultiDex.install(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.paigramteam.nomihoyoapp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.paigramteam.nomihoyoapp.ui.ByWebViewActivity;
|
||||
import com.paigramteam.nomihoyoapp.utils.StatusBarUtil;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
|
||||
// 是否开启了主页,没有开启则会返回主页
|
||||
public static boolean isLaunch = false;
|
||||
private int state = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
StatusBarUtil.setColor(this, ContextCompat.getColor(this, R.color.colorPrimary), 0);
|
||||
initView();
|
||||
isLaunch = true;
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
findViewById(R.id.bt_openUrl).setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.bt_openUrl) {
|
||||
openUrl();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开网页
|
||||
*/
|
||||
private void openUrl() {
|
||||
state = 0;
|
||||
loadUrl("https://user.mihoyo.com", "MiHoYo");
|
||||
}
|
||||
|
||||
private void loadUrl(String mUrl, String mTitle) {
|
||||
ByWebViewActivity.loadUrl(this, mUrl, mTitle, state);
|
||||
}
|
||||
|
||||
public static void start(Context context) {
|
||||
context.startActivity(new Intent(context, MainActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
isLaunch = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package com.paigramteam.nomihoyoapp.config;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.webkit.JavascriptInterface;
|
||||
|
||||
import com.paigramteam.nomihoyoapp.utils.WebTools;
|
||||
|
||||
/**
|
||||
* Created by jingbin on 2016/11/17.
|
||||
* js通信接口
|
||||
*/
|
||||
public class MyJavascriptInterface {
|
||||
|
||||
private Activity activity;
|
||||
|
||||
public MyJavascriptInterface(Activity context) {
|
||||
this.activity = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端代码嵌入js:
|
||||
* imageClick 名应和js函数方法名一致
|
||||
*
|
||||
* @param src 图片的链接
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void imageClick(String src) {
|
||||
// 在子线程
|
||||
Log.e("imageClick", "----点击了图片");
|
||||
Log.e("---src", src);
|
||||
WebTools.showToast(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端代码嵌入js
|
||||
* 遍历<li>节点
|
||||
*
|
||||
* @param type <li>节点下type属性的值
|
||||
* @param item_pk item_pk属性的值
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void textClick(String type, String item_pk) {
|
||||
if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(item_pk)) {
|
||||
Log.e("textClick", "----点击了文字");
|
||||
Log.e("type", type);
|
||||
Log.e("item_pk", item_pk);
|
||||
WebTools.showToast("type: " + type + ", item_pk:" + item_pk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页使用的js,方法无参数
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void startFunction() {
|
||||
Log.e("startFunction", "----无参");
|
||||
WebTools.showToast("无参方法");
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页使用的js,方法有参数,且参数名为data
|
||||
*
|
||||
* @param data 网页js里的参数名
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void startFunction(String data) {
|
||||
Log.e("startFunction", "----有参方法: " + data);
|
||||
WebTools.showToast("----有参方法: " + data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网页源代码
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void showSource(String html) {
|
||||
Log.e("showSourceCode", html);
|
||||
}
|
||||
}
|
@ -0,0 +1,295 @@
|
||||
package com.paigramteam.nomihoyoapp.ui;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.paigramteam.nomihoyoapp.MainActivity;
|
||||
import com.paigramteam.nomihoyoapp.R;
|
||||
import com.paigramteam.nomihoyoapp.config.MyJavascriptInterface;
|
||||
import com.paigramteam.nomihoyoapp.utils.StatusBarUtil;
|
||||
import com.paigramteam.nomihoyoapp.utils.WebTools;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import me.jingbin.web.ByWebTools;
|
||||
import me.jingbin.web.ByWebView;
|
||||
import me.jingbin.web.OnByWebClientCallback;
|
||||
import me.jingbin.web.OnTitleProgressCallback;
|
||||
|
||||
/**
|
||||
* 网页可以处理:
|
||||
* 点击相应控件:
|
||||
* - 进度条显示
|
||||
* - 上传图片(版本兼容)
|
||||
* - 全屏播放网络视频
|
||||
* - 唤起微信支付宝
|
||||
* - 拨打电话、发送短信、发送邮件
|
||||
* - 返回网页上一层、显示网页标题
|
||||
* JS交互部分:
|
||||
* - 前端代码嵌入js(缺乏灵活性)
|
||||
* - 网页自带js跳转
|
||||
* 被作为第三方浏览器打开
|
||||
*
|
||||
* @author jingbin
|
||||
* link to https://github.com/youlookwhat/ByWebView
|
||||
*/
|
||||
public class ByWebViewActivity extends AppCompatActivity {
|
||||
|
||||
// 网页链接
|
||||
private int mState;
|
||||
private String mUrl;
|
||||
private String mTitle;
|
||||
private WebView webView;
|
||||
private ByWebView byWebView;
|
||||
private TextView tvGunTitle;
|
||||
private String cookies;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_by_webview);
|
||||
getIntentData();
|
||||
initTitle();
|
||||
getDataFromBrowser(getIntent());
|
||||
}
|
||||
|
||||
private void getIntentData() {
|
||||
mUrl = getIntent().getStringExtra("url");
|
||||
mTitle = getIntent().getStringExtra("title");
|
||||
mState = getIntent().getIntExtra("state", 0);
|
||||
}
|
||||
|
||||
private void initTitle() {
|
||||
StatusBarUtil.setColor(this, ContextCompat.getColor(this, R.color.colorPrimary), 0);
|
||||
initToolBar();
|
||||
LinearLayout container = findViewById(R.id.ll_container);
|
||||
byWebView = ByWebView
|
||||
.with(this)
|
||||
.setWebParent(container, new LinearLayout.LayoutParams(-1, -1))
|
||||
.useWebProgress(ContextCompat.getColor(this, R.color.colorRed))
|
||||
.setOnTitleProgressCallback(onTitleProgressCallback)
|
||||
.setOnByWebClientCallback(onByWebClientCallback)
|
||||
.addJavascriptInterface("injectedObject", new MyJavascriptInterface(this))
|
||||
.loadUrl(mUrl);
|
||||
webView = byWebView.getWebView();
|
||||
}
|
||||
|
||||
private void initToolBar() {
|
||||
// 可滚动的title 使用简单 没有渐变效果,文字两旁有阴影
|
||||
Toolbar mTitleToolBar = findViewById(R.id.title_tool_bar);
|
||||
tvGunTitle = findViewById(R.id.tv_gun_title);
|
||||
setSupportActionBar(mTitleToolBar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
//去除默认Title显示
|
||||
actionBar.setDisplayShowTitleEnabled(false);
|
||||
}
|
||||
mTitleToolBar.setOverflowIcon(ContextCompat.getDrawable(this, R.drawable.actionbar_more));
|
||||
tvGunTitle.postDelayed(() -> tvGunTitle.setSelected(true), 1900);
|
||||
tvGunTitle.setText(mTitle);
|
||||
}
|
||||
|
||||
private OnTitleProgressCallback onTitleProgressCallback = new OnTitleProgressCallback() {
|
||||
@Override
|
||||
public void onReceivedTitle(String title) {
|
||||
Log.e("---title", title);
|
||||
tvGunTitle.setText(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全屏显示时处理横竖屏。
|
||||
* 默认返回false,全屏时为横屏,全屏还原后为竖屏
|
||||
* 如果要手动处理,需要返回true!
|
||||
*
|
||||
* @param isShow 是否显示了全屏视频 true点击了全屏显示,false全屏视频还原
|
||||
*/
|
||||
@Override
|
||||
public boolean onHandleScreenOrientation(boolean isShow) {
|
||||
return super.onHandleScreenOrientation(isShow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义实现 onJsAlert 方法,如果不自定义可不实现此方法
|
||||
* 一定要执行 result.confirm();
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
||||
Dialog alertDialog = new AlertDialog.Builder(view.getContext()).
|
||||
setTitle("自定义标题").
|
||||
setMessage(message).
|
||||
setCancelable(false).
|
||||
setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
result.confirm();
|
||||
}
|
||||
})
|
||||
.create();
|
||||
alertDialog.show();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private OnByWebClientCallback onByWebClientCallback = new OnByWebClientCallback() {
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
Log.e("---onPageStarted", url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
cookies = cookieManager.getCookie(url);
|
||||
super.onPageFinished(view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpenThirdApp(String url) {
|
||||
// 处理三方链接
|
||||
Log.e("---url", url);
|
||||
return ByWebTools.handleThirdApp(ByWebViewActivity.this, url);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_webview, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int item_id = item.getItemId();
|
||||
if (item_id == android.R.id.home) {
|
||||
handleFinish();
|
||||
} else if (item_id == R.id.actionbar_cope) {
|
||||
WebTools.copy(cookies);
|
||||
Toast.makeText(this, "复制成功", Toast.LENGTH_LONG).show();
|
||||
} else if (item_id == R.id.actionbar_webview_refresh) {
|
||||
byWebView.reload();
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用singleTask启动模式的Activity在系统中只会存在一个实例。
|
||||
* 如果这个实例已经存在,intent就会通过onNewIntent传递到这个Activity。
|
||||
* 否则新的Activity实例被创建。
|
||||
*/
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
getDataFromBrowser(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 作为三方浏览器打开传过来的值
|
||||
* Scheme: https
|
||||
* host: www.jianshu.com
|
||||
* path: /p/1cbaf784c29c
|
||||
* url = scheme + "://" + host + path;
|
||||
*/
|
||||
private void getDataFromBrowser(Intent intent) {
|
||||
Uri data = intent.getData();
|
||||
if (data != null) {
|
||||
try {
|
||||
String scheme = data.getScheme();
|
||||
String host = data.getHost();
|
||||
String path = data.getPath();
|
||||
String text = "Scheme: " + scheme + "\n" + "host: " + host + "\n" + "path: " + path;
|
||||
Log.e("data", text);
|
||||
String url = scheme + "://" + host + path;
|
||||
byWebView.loadUrl(url);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接通过三方浏览器打开时,回退到首页
|
||||
*/
|
||||
public void handleFinish() {
|
||||
supportFinishAfterTransition();
|
||||
if (!MainActivity.isLaunch) {
|
||||
MainActivity.start(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (byWebView.handleKeyEvent(keyCode, event)) {
|
||||
return true;
|
||||
} else {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
handleFinish();
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
byWebView.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
byWebView.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
byWebView.onDestroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开网页:
|
||||
*
|
||||
* @param mContext 上下文
|
||||
* @param url 要加载的网页url
|
||||
* @param title 标题
|
||||
* @param state 类型
|
||||
*/
|
||||
public static void loadUrl(Context mContext, String url, String title, int state) {
|
||||
Intent intent = new Intent(mContext, ByWebViewActivity.class);
|
||||
intent.putExtra("url", url);
|
||||
intent.putExtra("state", state);
|
||||
intent.putExtra("title", title == null ? "加载中..." : title);
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.paigramteam.nomihoyoapp.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
public class ProcessUtils {
|
||||
|
||||
public static String getCurrentProcessName(Context context) {
|
||||
String name = getCurrentProcessNameByFile();
|
||||
if (!TextUtils.isEmpty(name)) return name;
|
||||
name = getCurrentProcessNameByAms(context);
|
||||
if (!TextUtils.isEmpty(name)) return name;
|
||||
name = getCurrentProcessNameByReflect(context);
|
||||
return name;
|
||||
}
|
||||
|
||||
private static String getCurrentProcessNameByFile() {
|
||||
try {
|
||||
File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
|
||||
BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
|
||||
String processName = mBufferedReader.readLine().trim();
|
||||
mBufferedReader.close();
|
||||
return processName;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getCurrentProcessNameByAms(Context context) {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (am == null) return "";
|
||||
List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();
|
||||
if (info == null || info.size() == 0) return "";
|
||||
int pid = android.os.Process.myPid();
|
||||
for (ActivityManager.RunningAppProcessInfo aInfo : info) {
|
||||
if (aInfo.pid == pid) {
|
||||
if (aInfo.processName != null) {
|
||||
return aInfo.processName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String getCurrentProcessNameByReflect(Context context) {
|
||||
String processName = "";
|
||||
try {
|
||||
Application app = (Application) context.getApplicationContext();
|
||||
Field loadedApkField = app.getClass().getField("mLoadedApk");
|
||||
loadedApkField.setAccessible(true);
|
||||
Object loadedApk = loadedApkField.get(app);
|
||||
|
||||
Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread");
|
||||
activityThreadField.setAccessible(true);
|
||||
Object activityThread = activityThreadField.get(loadedApk);
|
||||
|
||||
Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName");
|
||||
processName = (String) getProcessName.invoke(activityThread);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return processName;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,448 @@
|
||||
package com.paigramteam.nomihoyoapp.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Properties;
|
||||
|
||||
public class RomUtils {
|
||||
|
||||
private static final String[] ROM_HUAWEI = {"huawei"};
|
||||
private static final String[] ROM_VIVO = {"vivo"};
|
||||
private static final String[] ROM_XIAOMI = {"xiaomi"};
|
||||
private static final String[] ROM_OPPO = {"oppo"};
|
||||
private static final String[] ROM_LEECO = {"leeco", "letv"};
|
||||
private static final String[] ROM_360 = {"360", "qiku"};
|
||||
private static final String[] ROM_ZTE = {"zte"};
|
||||
private static final String[] ROM_ONEPLUS = {"oneplus"};
|
||||
private static final String[] ROM_NUBIA = {"nubia"};
|
||||
private static final String[] ROM_COOLPAD = {"coolpad", "yulong"};
|
||||
private static final String[] ROM_LG = {"lg", "lge"};
|
||||
private static final String[] ROM_GOOGLE = {"google"};
|
||||
private static final String[] ROM_SAMSUNG = {"samsung"};
|
||||
private static final String[] ROM_MEIZU = {"meizu"};
|
||||
private static final String[] ROM_LENOVO = {"lenovo"};
|
||||
private static final String[] ROM_SMARTISAN = {"smartisan", "deltainno"};
|
||||
private static final String[] ROM_HTC = {"htc"};
|
||||
private static final String[] ROM_SONY = {"sony"};
|
||||
private static final String[] ROM_GIONEE = {"gionee", "amigo"};
|
||||
private static final String[] ROM_MOTOROLA = {"motorola"};
|
||||
|
||||
private static final String VERSION_PROPERTY_HUAWEI = "ro.build.version.emui";
|
||||
private static final String VERSION_PROPERTY_VIVO = "ro.vivo.os.build.display.id";
|
||||
private static final String VERSION_PROPERTY_XIAOMI = "ro.build.version.incremental";
|
||||
private static final String VERSION_PROPERTY_OPPO = "ro.build.version.opporom";
|
||||
private static final String VERSION_PROPERTY_LEECO = "ro.letv.release.version";
|
||||
private static final String VERSION_PROPERTY_360 = "ro.build.uiversion";
|
||||
private static final String VERSION_PROPERTY_ZTE = "ro.build.MiFavor_version";
|
||||
private static final String VERSION_PROPERTY_ONEPLUS = "ro.rom.version";
|
||||
private static final String VERSION_PROPERTY_NUBIA = "ro.build.rom.id";
|
||||
private final static String UNKNOWN = "unknown";
|
||||
|
||||
private static RomInfo bean = null;
|
||||
|
||||
private RomUtils() {
|
||||
throw new UnsupportedOperationException("u can't instantiate me...");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by huawei.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isHuawei() {
|
||||
return ROM_HUAWEI[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by vivo.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isVivo() {
|
||||
return ROM_VIVO[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by xiaomi.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isXiaomi() {
|
||||
return ROM_XIAOMI[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by oppo.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isOppo() {
|
||||
return ROM_OPPO[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by leeco.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isLeeco() {
|
||||
return ROM_LEECO[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by 360.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean is360() {
|
||||
return ROM_360[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by zte.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isZte() {
|
||||
return ROM_ZTE[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by oneplus.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isOneplus() {
|
||||
return ROM_ONEPLUS[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by nubia.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isNubia() {
|
||||
return ROM_NUBIA[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by coolpad.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isCoolpad() {
|
||||
return ROM_COOLPAD[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by lg.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isLg() {
|
||||
return ROM_LG[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by google.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isGoogle() {
|
||||
return ROM_GOOGLE[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by samsung.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isSamsung() {
|
||||
return ROM_SAMSUNG[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by meizu.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isMeizu() {
|
||||
return ROM_MEIZU[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by lenovo.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isLenovo() {
|
||||
return ROM_LENOVO[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by smartisan.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isSmartisan() {
|
||||
return ROM_SMARTISAN[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by htc.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isHtc() {
|
||||
return ROM_HTC[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by sony.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isSony() {
|
||||
return ROM_SONY[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by gionee.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isGionee() {
|
||||
return ROM_GIONEE[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rom is made by motorola.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isMotorola() {
|
||||
return ROM_MOTOROLA[0].equals(getRomInfo().name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rom's information.
|
||||
*
|
||||
* @return the rom's information
|
||||
*/
|
||||
public static RomInfo getRomInfo() {
|
||||
if (bean != null) return bean;
|
||||
bean = new RomInfo();
|
||||
final String brand = getBrand();
|
||||
final String manufacturer = getManufacturer();
|
||||
if (isRightRom(brand, manufacturer, ROM_HUAWEI)) {
|
||||
bean.name = ROM_HUAWEI[0];
|
||||
String version = getRomVersion(VERSION_PROPERTY_HUAWEI);
|
||||
String[] temp = version.split("_");
|
||||
if (temp.length > 1) {
|
||||
bean.version = temp[1];
|
||||
} else {
|
||||
bean.version = version;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
if (isRightRom(brand, manufacturer, ROM_VIVO)) {
|
||||
bean.name = ROM_VIVO[0];
|
||||
bean.version = getRomVersion(VERSION_PROPERTY_VIVO);
|
||||
return bean;
|
||||
}
|
||||
if (isRightRom(brand, manufacturer, ROM_XIAOMI)) {
|
||||
bean.name = ROM_XIAOMI[0];
|
||||
bean.version = getRomVersion(VERSION_PROPERTY_XIAOMI);
|
||||
return bean;
|
||||
}
|
||||
if (isRightRom(brand, manufacturer, ROM_OPPO)) {
|
||||
bean.name = ROM_OPPO[0];
|
||||
bean.version = getRomVersion(VERSION_PROPERTY_OPPO);
|
||||
return bean;
|
||||
}
|
||||
if (isRightRom(brand, manufacturer, ROM_LEECO)) {
|
||||
bean.name = ROM_LEECO[0];
|
||||
bean.version = getRomVersion(VERSION_PROPERTY_LEECO);
|
||||
return bean;
|
||||
}
|
||||
|
||||
if (isRightRom(brand, manufacturer, ROM_360)) {
|
||||
bean.name = ROM_360[0];
|
||||
bean.version = getRomVersion(VERSION_PROPERTY_360);
|
||||
return bean;
|
||||
}
|
||||
if (isRightRom(brand, manufacturer, ROM_ZTE)) {
|
||||
bean.name = ROM_ZTE[0];
|
||||
bean.version = getRomVersion(VERSION_PROPERTY_ZTE);
|
||||
return bean;
|
||||
}
|
||||
if (isRightRom(brand, manufacturer, ROM_ONEPLUS)) {
|
||||
bean.name = ROM_ONEPLUS[0];
|
||||
bean.version = getRomVersion(VERSION_PROPERTY_ONEPLUS);
|
||||
return bean;
|
||||
}
|
||||
if (isRightRom(brand, manufacturer, ROM_NUBIA)) {
|
||||
bean.name = ROM_NUBIA[0];
|
||||
bean.version = getRomVersion(VERSION_PROPERTY_NUBIA);
|
||||
return bean;
|
||||
}
|
||||
|
||||
if (isRightRom(brand, manufacturer, ROM_COOLPAD)) {
|
||||
bean.name = ROM_COOLPAD[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_LG)) {
|
||||
bean.name = ROM_LG[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_GOOGLE)) {
|
||||
bean.name = ROM_GOOGLE[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_SAMSUNG)) {
|
||||
bean.name = ROM_SAMSUNG[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_MEIZU)) {
|
||||
bean.name = ROM_MEIZU[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_LENOVO)) {
|
||||
bean.name = ROM_LENOVO[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_SMARTISAN)) {
|
||||
bean.name = ROM_SMARTISAN[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_HTC)) {
|
||||
bean.name = ROM_HTC[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_SONY)) {
|
||||
bean.name = ROM_SONY[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_GIONEE)) {
|
||||
bean.name = ROM_GIONEE[0];
|
||||
} else if (isRightRom(brand, manufacturer, ROM_MOTOROLA)) {
|
||||
bean.name = ROM_MOTOROLA[0];
|
||||
} else {
|
||||
bean.name = manufacturer;
|
||||
}
|
||||
bean.version = getRomVersion("");
|
||||
return bean;
|
||||
}
|
||||
|
||||
private static boolean isRightRom(final String brand, final String manufacturer, final String... names) {
|
||||
for (String name : names) {
|
||||
if (brand.contains(name) || manufacturer.contains(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String getManufacturer() {
|
||||
try {
|
||||
String manufacturer = Build.MANUFACTURER;
|
||||
if (!TextUtils.isEmpty(manufacturer)) {
|
||||
return manufacturer.toLowerCase();
|
||||
}
|
||||
} catch (Throwable ignore) {/**/}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
private static String getBrand() {
|
||||
try {
|
||||
String brand = Build.BRAND;
|
||||
if (!TextUtils.isEmpty(brand)) {
|
||||
return brand.toLowerCase();
|
||||
}
|
||||
} catch (Throwable ignore) {/**/}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
private static String getRomVersion(final String propertyName) {
|
||||
String ret = "";
|
||||
if (!TextUtils.isEmpty(propertyName)) {
|
||||
ret = getSystemProperty(propertyName);
|
||||
}
|
||||
if (TextUtils.isEmpty(ret) || ret.equals(UNKNOWN)) {
|
||||
try {
|
||||
String display = Build.DISPLAY;
|
||||
if (!TextUtils.isEmpty(display)) {
|
||||
ret = display.toLowerCase();
|
||||
}
|
||||
} catch (Throwable ignore) {/**/}
|
||||
}
|
||||
if (TextUtils.isEmpty(ret)) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static String getSystemProperty(final String name) {
|
||||
String prop = getSystemPropertyByShell(name);
|
||||
if (!TextUtils.isEmpty(prop)) return prop;
|
||||
prop = getSystemPropertyByStream(name);
|
||||
if (!TextUtils.isEmpty(prop)) return prop;
|
||||
if (Build.VERSION.SDK_INT < 28) {
|
||||
return getSystemPropertyByReflect(name);
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
|
||||
private static String getSystemPropertyByShell(final String propName) {
|
||||
String line;
|
||||
BufferedReader input = null;
|
||||
try {
|
||||
Process p = Runtime.getRuntime().exec("getprop " + propName);
|
||||
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
|
||||
String ret = input.readLine();
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
} catch (IOException ignore) {
|
||||
} finally {
|
||||
if (input != null) {
|
||||
try {
|
||||
input.close();
|
||||
} catch (IOException ignore) {/**/}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String getSystemPropertyByStream(final String key) {
|
||||
try {
|
||||
Properties prop = new Properties();
|
||||
FileInputStream is = new FileInputStream(
|
||||
new File(Environment.getRootDirectory(), "build.prop")
|
||||
);
|
||||
prop.load(is);
|
||||
return prop.getProperty(key, "");
|
||||
} catch (Exception ignore) {/**/}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String getSystemPropertyByReflect(String key) {
|
||||
try {
|
||||
@SuppressLint("PrivateApi")
|
||||
Class<?> clz = Class.forName("android.os.SystemProperties");
|
||||
Method getMethod = clz.getMethod("get", String.class, String.class);
|
||||
return (String) getMethod.invoke(clz, key, "");
|
||||
} catch (Exception e) {/**/}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static class RomInfo {
|
||||
private String name;
|
||||
private String version;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RomInfo{name=" + name +
|
||||
", version=" + version + "}";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,510 @@
|
||||
package com.paigramteam.nomihoyoapp.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.TabActivity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
||||
/**
|
||||
* Created by Jaeger on 16/2/14.
|
||||
* <p>
|
||||
* Email: chjie.jaeger@gmail.com
|
||||
* GitHub: https://github.com/laobie
|
||||
*/
|
||||
public class StatusBarUtil {
|
||||
|
||||
public static final int DEFAULT_STATUS_BAR_ALPHA = 112;
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
public static void setColor(Activity activity, @ColorInt int color) {
|
||||
setColor(activity, color, DEFAULT_STATUS_BAR_ALPHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param color 状态栏颜色值
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
|
||||
public static void setColor(Activity activity, @ColorInt int color, int statusBarAlpha) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏纯色 不加半透明效果
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
public static void setColorNoTranslucent(Activity activity, @ColorInt int color) {
|
||||
setColor(activity, color, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色(5.0以下无半透明效果,不建议使用)
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setColorDiff(Activity activity, @ColorInt int color) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
// 生成一个状态栏大小的矩形
|
||||
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
|
||||
int count = decorView.getChildCount();
|
||||
if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
|
||||
decorView.getChildAt(count - 1).setBackgroundColor(color);
|
||||
} else {
|
||||
StatusBarView statusView = createStatusBarView(activity, color);
|
||||
decorView.addView(statusView);
|
||||
}
|
||||
setRootView(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使状态栏半透明
|
||||
* <p>
|
||||
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
*/
|
||||
public static void setTranslucent(Activity activity) {
|
||||
setTranslucent(activity, DEFAULT_STATUS_BAR_ALPHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使状态栏半透明
|
||||
* <p>
|
||||
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
public static void setTranslucent(Activity activity, int statusBarAlpha) {
|
||||
setTransparent(activity);
|
||||
addTranslucentView(activity, statusBarAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* 针对根布局是 CoordinatorLayout, 使状态栏半透明
|
||||
* <p>
|
||||
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
public static void setTranslucentForCoordinatorLayout(Activity activity, int statusBarAlpha) {
|
||||
transparentStatusBar(activity);
|
||||
addTranslucentView(activity, statusBarAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏全透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
*/
|
||||
public static void setTransparent(Activity activity) {
|
||||
transparentStatusBar(activity);
|
||||
setRootView(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使状态栏透明(5.0以上半透明效果,不建议使用)
|
||||
* <p>
|
||||
* 适用于图片作为背景的界面,此时需要图片填充到状态栏
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setTranslucentDiff(Activity activity) {
|
||||
// 设置状态栏透明
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
setRootView(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DrawerLayout 布局设置状态栏变色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) {
|
||||
setColorForDrawerLayout(activity, drawerLayout, color, DEFAULT_STATUS_BAR_ALPHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DrawerLayout 布局设置状态栏颜色,纯色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
public static void setColorNoTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) {
|
||||
setColorForDrawerLayout(activity, drawerLayout, color, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DrawerLayout 布局设置状态栏变色
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param color 状态栏颜色值
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
*/
|
||||
public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color,
|
||||
int statusBarAlpha) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
// 生成一个状态栏大小的矩形
|
||||
// 添加 statusBarView 到布局中
|
||||
ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0);
|
||||
if (contentLayout.getChildCount() > 0 && contentLayout.getChildAt(0) instanceof StatusBarView) {
|
||||
contentLayout.getChildAt(0).setBackgroundColor(calculateStatusColor(color, statusBarAlpha));
|
||||
} else {
|
||||
StatusBarView statusBarView = createStatusBarView(activity, color);
|
||||
contentLayout.addView(statusBarView, 0);
|
||||
}
|
||||
// 内容布局不是 LinearLayout 时,设置padding top
|
||||
if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) {
|
||||
contentLayout.getChildAt(1)
|
||||
.setPadding(contentLayout.getPaddingLeft(), getStatusBarHeight(activity) + contentLayout.getPaddingTop(),
|
||||
contentLayout.getPaddingRight(), contentLayout.getPaddingBottom());
|
||||
}
|
||||
// 设置属性
|
||||
ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1);
|
||||
drawerLayout.setFitsSystemWindows(false);
|
||||
contentLayout.setFitsSystemWindows(false);
|
||||
contentLayout.setClipToPadding(true);
|
||||
drawer.setFitsSystemWindows(false);
|
||||
|
||||
addTranslucentView(activity, statusBarAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DrawerLayout 布局设置状态栏变色(5.0以下无半透明效果,不建议使用)
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
* @param color 状态栏颜色值
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setColorForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
// 生成一个状态栏大小的矩形
|
||||
ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0);
|
||||
if (contentLayout.getChildCount() > 0 && contentLayout.getChildAt(0) instanceof StatusBarView) {
|
||||
contentLayout.getChildAt(0).setBackgroundColor(calculateStatusColor(color, DEFAULT_STATUS_BAR_ALPHA));
|
||||
} else {
|
||||
// 添加 statusBarView 到布局中
|
||||
StatusBarView statusBarView = createStatusBarView(activity, color);
|
||||
contentLayout.addView(statusBarView, 0);
|
||||
}
|
||||
// 内容布局不是 LinearLayout 时,设置padding top
|
||||
if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) {
|
||||
contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0);
|
||||
}
|
||||
// 设置属性
|
||||
ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1);
|
||||
drawerLayout.setFitsSystemWindows(false);
|
||||
contentLayout.setFitsSystemWindows(false);
|
||||
contentLayout.setClipToPadding(true);
|
||||
drawer.setFitsSystemWindows(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 DrawerLayout 布局设置状态栏透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
*/
|
||||
public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) {
|
||||
setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_STATUS_BAR_ALPHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 DrawerLayout 布局设置状态栏透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
*/
|
||||
public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, int statusBarAlpha) {
|
||||
setTransparentForDrawerLayout(activity, drawerLayout);
|
||||
addTranslucentView(activity, statusBarAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 DrawerLayout 布局设置状态栏透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
*/
|
||||
public static void setTransparentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
|
||||
ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0);
|
||||
// 内容布局不是 LinearLayout 时,设置padding top
|
||||
if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) {
|
||||
contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0);
|
||||
}
|
||||
|
||||
// 设置属性
|
||||
ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1);
|
||||
drawerLayout.setFitsSystemWindows(false);
|
||||
contentLayout.setFitsSystemWindows(false);
|
||||
contentLayout.setClipToPadding(true);
|
||||
drawer.setFitsSystemWindows(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 DrawerLayout 布局设置状态栏透明(5.0以上半透明效果,不建议使用)
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param drawerLayout DrawerLayout
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setTranslucentForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout) {
|
||||
// 设置状态栏透明
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
// 设置内容布局属性
|
||||
ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0);
|
||||
contentLayout.setFitsSystemWindows(true);
|
||||
contentLayout.setClipToPadding(true);
|
||||
// 设置抽屉布局属性
|
||||
ViewGroup vg = (ViewGroup) drawerLayout.getChildAt(1);
|
||||
vg.setFitsSystemWindows(false);
|
||||
// 设置 DrawerLayout 属性
|
||||
drawerLayout.setFitsSystemWindows(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为头部是 ImageView 的界面设置状态栏全透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
public static void setTransparentForImageView(Activity activity, View needOffsetView) {
|
||||
setTranslucentForImageView(activity, 0, needOffsetView);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为头部是 ImageView 的界面设置状态栏透明(使用默认透明度)
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
public static void setTranslucentForImageView(Activity activity, View needOffsetView) {
|
||||
setTranslucentForImageView(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为头部是 ImageView 的界面设置状态栏透明
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
public static void setTranslucentForImageView(Activity activity, int statusBarAlpha, View needOffsetView) {
|
||||
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
activity.getWindow()
|
||||
.getDecorView()
|
||||
.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||
if (activity instanceof TabActivity){
|
||||
activity.getWindow()//兼容TabActivity
|
||||
.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
addTranslucentView(activity, statusBarAlpha);
|
||||
if (needOffsetView != null) {
|
||||
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams();
|
||||
if (layoutParams != null) {
|
||||
layoutParams.setMargins(0, getStatusBarHeight(activity), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setMargin(Activity activity, View needOffsetView) {
|
||||
if (needOffsetView != null) {
|
||||
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams();
|
||||
if (layoutParams != null) {
|
||||
layoutParams.setMargins(0, getStatusBarHeight(activity), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为 fragment 头部是 ImageView 的设置状态栏透明
|
||||
*
|
||||
* @param activity fragment 对应的 activity
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
public static void setTranslucentForImageViewInFragment(Activity activity, View needOffsetView) {
|
||||
setTranslucentForImageViewInFragment(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 fragment 头部是 ImageView 的设置状态栏透明
|
||||
*
|
||||
* @param activity fragment 对应的 activity
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
public static void setTransparentForImageViewInFragment(Activity activity, View needOffsetView) {
|
||||
setTranslucentForImageViewInFragment(activity, 0, needOffsetView);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 fragment 头部是 ImageView 的设置状态栏透明
|
||||
*
|
||||
* @param activity fragment 对应的 activity
|
||||
* @param statusBarAlpha 状态栏透明度
|
||||
* @param needOffsetView 需要向下偏移的 View
|
||||
*/
|
||||
public static void setTranslucentForImageViewInFragment(Activity activity, int statusBarAlpha, View needOffsetView) {
|
||||
setTranslucentForImageView(activity, statusBarAlpha, needOffsetView);
|
||||
}
|
||||
|
||||
private static void clearPreviousSetting(Activity activity) {
|
||||
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
|
||||
int count = decorView.getChildCount();
|
||||
if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
|
||||
decorView.removeViewAt(count - 1);
|
||||
ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
|
||||
rootView.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加半透明矩形条
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param statusBarAlpha 透明值
|
||||
*/
|
||||
private static void addTranslucentView(Activity activity, int statusBarAlpha) {
|
||||
ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content);
|
||||
if (contentView.getChildCount() > 1) {
|
||||
contentView.getChildAt(1).setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0));
|
||||
} else {
|
||||
contentView.addView(createTranslucentStatusBarView(activity, statusBarAlpha));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个和状态栏大小相同的彩色矩形条
|
||||
*
|
||||
* @param activity 需要设置的 activity
|
||||
* @param color 状态栏颜色值
|
||||
* @return 状态栏矩形条
|
||||
*/
|
||||
private static StatusBarView createStatusBarView(Activity activity, @ColorInt int color) {
|
||||
// 绘制一个和状态栏一样高的矩形
|
||||
StatusBarView statusBarView = new StatusBarView(activity);
|
||||
LinearLayout.LayoutParams params =
|
||||
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
|
||||
statusBarView.setLayoutParams(params);
|
||||
statusBarView.setBackgroundColor(color);
|
||||
return statusBarView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个和状态栏大小相同的半透明矩形条
|
||||
*
|
||||
* @param activity 需要设置的activity
|
||||
* @param color 状态栏颜色值
|
||||
* @param alpha 透明值
|
||||
* @return 状态栏矩形条
|
||||
*/
|
||||
private static StatusBarView createStatusBarView(Activity activity, @ColorInt int color, int alpha) {
|
||||
// 绘制一个和状态栏一样高的矩形
|
||||
StatusBarView statusBarView = new StatusBarView(activity);
|
||||
LinearLayout.LayoutParams params =
|
||||
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
|
||||
statusBarView.setLayoutParams(params);
|
||||
statusBarView.setBackgroundColor(calculateStatusColor(color, alpha));
|
||||
return statusBarView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根布局参数
|
||||
*/
|
||||
private static void setRootView(Activity activity) {
|
||||
ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
|
||||
rootView.setFitsSystemWindows(true);
|
||||
rootView.setClipToPadding(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使状态栏透明
|
||||
*/
|
||||
private static void transparentStatusBar(Activity activity) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
|
||||
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建半透明矩形 View
|
||||
*
|
||||
* @param alpha 透明值
|
||||
* @return 半透明 View
|
||||
*/
|
||||
private static StatusBarView createTranslucentStatusBarView(Activity activity, int alpha) {
|
||||
// 绘制一个和状态栏一样高的矩形
|
||||
StatusBarView statusBarView = new StatusBarView(activity);
|
||||
LinearLayout.LayoutParams params =
|
||||
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
|
||||
statusBarView.setLayoutParams(params);
|
||||
statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
|
||||
return statusBarView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态栏高度
|
||||
*
|
||||
* @param context context
|
||||
* @return 状态栏高度
|
||||
*/
|
||||
public static int getStatusBarHeight(Context context) {
|
||||
// 获得状态栏高度
|
||||
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
return context.getResources().getDimensionPixelSize(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算状态栏颜色
|
||||
*
|
||||
* @param color color值
|
||||
* @param alpha alpha值
|
||||
* @return 最终的状态栏颜色
|
||||
*/
|
||||
private static int calculateStatusColor(@ColorInt int color, int alpha) {
|
||||
float a = 1 - alpha / 255f;
|
||||
int red = color >> 16 & 0xff;
|
||||
int green = color >> 8 & 0xff;
|
||||
int blue = color & 0xff;
|
||||
red = (int) (red * a + 0.5);
|
||||
green = (int) (green * a + 0.5);
|
||||
blue = (int) (blue * a + 0.5);
|
||||
return 0xff << 24 | red << 16 | green << 8 | blue;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.paigramteam.nomihoyoapp.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by Jaeger on 16/6/8.
|
||||
*
|
||||
* Email: chjie.jaeger@gmail.com
|
||||
* GitHub: https://github.com/laobie
|
||||
*/
|
||||
public class StatusBarView extends View {
|
||||
public StatusBarView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public StatusBarView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
package com.paigramteam.nomihoyoapp.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.paigramteam.nomihoyoapp.App;
|
||||
import com.paigramteam.nomihoyoapp.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by jingbin on 2017/2/13.
|
||||
*/
|
||||
|
||||
public class WebTools {
|
||||
|
||||
/**
|
||||
* 实现文本复制功能
|
||||
*
|
||||
* @param content 复制的文本
|
||||
*/
|
||||
public static void copy(String content) {
|
||||
if (!TextUtils.isEmpty(content)) {
|
||||
ClipboardManager clipboard = (ClipboardManager) App.getInstance().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(content, content);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用浏览器打开链接
|
||||
*/
|
||||
public static void openLink(Context context, String content) {
|
||||
if (!TextUtils.isEmpty(content) && content.startsWith("http")) {
|
||||
Uri issuesUrl = Uri.parse(content);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, issuesUrl);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过包名找应用,不需要权限
|
||||
*/
|
||||
public static boolean hasPackage(Context context, String packageName) {
|
||||
if (null == context || TextUtils.isEmpty(packageName)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_GIDS);
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// 抛出找不到的异常,说明该程序已经被卸载
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认处理流程:网页里可能唤起其他的app
|
||||
*/
|
||||
public static boolean handleThirdApp(Activity activity, String backUrl) {
|
||||
/**http开头直接跳过*/
|
||||
if (backUrl.startsWith("http")) {
|
||||
// 可能有提示下载Apk文件
|
||||
if (backUrl.contains(".apk")) {
|
||||
startActivity(activity, backUrl);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (backUrl.contains("alipays")) {
|
||||
// 网页跳支付宝支付
|
||||
if (hasPackage(activity, "com.eg.android.AlipayGphone")) {
|
||||
startActivity(activity, backUrl);
|
||||
}
|
||||
|
||||
} else if (backUrl.contains("weixin://wap/pay")) {
|
||||
// 微信支付
|
||||
if (hasPackage(activity, "com.tencent.mm")) {
|
||||
startActivity(activity, backUrl);
|
||||
}
|
||||
} else {
|
||||
|
||||
// 会唤起手机里有的App,如果不想被唤起,复制出来然后添加屏蔽即可
|
||||
boolean isJump = true;
|
||||
if (backUrl.contains("tbopen:")// 淘宝
|
||||
|| backUrl.contains("openapp.jdmobile:")// 京东
|
||||
|| backUrl.contains("jdmobile:")//京东
|
||||
|| backUrl.contains("zhihu:")// 知乎
|
||||
|| backUrl.contains("vipshop:")//
|
||||
|| backUrl.contains("youku:")//优酷
|
||||
|| backUrl.contains("uclink:")// UC
|
||||
|| backUrl.contains("ucbrowser:")// UC
|
||||
|| backUrl.contains("newsapp:")//
|
||||
|| backUrl.contains("sinaweibo:")// 新浪微博
|
||||
|| backUrl.contains("suning:")//
|
||||
|| backUrl.contains("pinduoduo:")// 拼多多
|
||||
|| backUrl.contains("qtt:")//
|
||||
|| backUrl.contains("baiduboxapp:")// 百度
|
||||
|| backUrl.contains("baiduboxlite:")// 百度
|
||||
|| backUrl.contains("baiduhaokan:")// 百度看看
|
||||
) {
|
||||
isJump = false;
|
||||
}
|
||||
if (isJump) {
|
||||
startActivity(activity, backUrl);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void startActivity(Activity context, String url) {
|
||||
try {
|
||||
|
||||
// 用于DeepLink测试
|
||||
if (url.startsWith("will://")) {
|
||||
Uri uri = Uri.parse(url);
|
||||
Log.e("---------scheme", uri.getScheme() + ";host: " + uri.getHost() + ";Id: " + uri.getPathSegments().get(0));
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(url));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static int dp2px(Context context, float dpValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
public static void showToast(String content) {
|
||||
if (!TextUtils.isEmpty(content)) {
|
||||
Toast.makeText(App.getInstance(), content, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 来自 https://github.com/Justson/AgentWeb/issues/934 建议
|
||||
* fix Using WebView from more than one process 多进程同时使用webview的bug
|
||||
*/
|
||||
public static void handleWebViewDir(Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Set<String> pathSet = new HashSet<>();
|
||||
String suffix = "";
|
||||
String dataPath = context.getDataDir().getAbsolutePath();
|
||||
String webViewDir = "/app_webview";
|
||||
String huaweiWebViewDir = "/app_hws_webview";
|
||||
String lockFile = "/webview_data.lock";
|
||||
String processName = ProcessUtils.getCurrentProcessName(context);
|
||||
if (!TextUtils.equals(context.getPackageName(), processName)) {//判断不等于默认进程名称
|
||||
suffix = TextUtils.isEmpty(processName) ? context.getPackageName() : processName;
|
||||
WebView.setDataDirectorySuffix(suffix);
|
||||
suffix = "_" + suffix;
|
||||
pathSet.add(dataPath + webViewDir + suffix + lockFile);
|
||||
if (RomUtils.isHuawei()) {
|
||||
pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile);
|
||||
}
|
||||
}else{
|
||||
//主进程
|
||||
suffix = "_" + processName;
|
||||
pathSet.add(dataPath + webViewDir + lockFile);//默认未添加进程名后缀
|
||||
pathSet.add(dataPath + webViewDir + suffix + lockFile);//系统自动添加了进程名后缀
|
||||
if (RomUtils.isHuawei()) {//部分华为手机更改了webview目录名
|
||||
pathSet.add(dataPath + huaweiWebViewDir + lockFile);
|
||||
pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile);
|
||||
}
|
||||
}
|
||||
for (String path : pathSet) {
|
||||
File file = new File(path);
|
||||
if (file.exists()) {
|
||||
tryLockOrRecreateFile(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
private static void tryLockOrRecreateFile(File file) {
|
||||
try {
|
||||
FileLock tryLock = new RandomAccessFile(file, "rw").getChannel().tryLock();
|
||||
if (tryLock != null) {
|
||||
tryLock.close();
|
||||
} else {
|
||||
createFile(file, file.delete());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
boolean deleted = false;
|
||||
if (file.exists()) {
|
||||
deleted = file.delete();
|
||||
}
|
||||
createFile(file, deleted);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createFile(File file, boolean deleted){
|
||||
try {
|
||||
if (deleted && !file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
18
app/src/main/java/me/jingbin/web/ByFullscreenHolder.java
Normal file
@ -0,0 +1,18 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class ByFullscreenHolder extends FrameLayout {
|
||||
|
||||
public ByFullscreenHolder(Context context) {
|
||||
super(context);
|
||||
setBackgroundColor(context.getResources().getColor(android.R.color.black));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return true;
|
||||
}
|
||||
}
|
102
app/src/main/java/me/jingbin/web/ByLoadJsHolder.java
Normal file
@ -0,0 +1,102 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Created by jingbin on 2020/7/4.
|
||||
*/
|
||||
public class ByLoadJsHolder {
|
||||
|
||||
private WebView mWebView;
|
||||
|
||||
ByLoadJsHolder(WebView webView) {
|
||||
this.mWebView = webView;
|
||||
}
|
||||
|
||||
public void loadJs(String js, final ValueCallback<String> callback) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
this.evaluateJs(js, callback);
|
||||
} else {
|
||||
mWebView.loadUrl(js);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadJs(String js) {
|
||||
loadJs(js, null);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
private void evaluateJs(String js, final ValueCallback<String> callback) {
|
||||
mWebView.evaluateJavascript(js, new ValueCallback<String>() {
|
||||
@Override
|
||||
public void onReceiveValue(String value) {
|
||||
if (callback != null) {
|
||||
callback.onReceiveValue(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void quickCallJs(String method, ValueCallback<String> callback, String... params) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("javascript:").append(method);
|
||||
if (params == null || params.length == 0) {
|
||||
sb.append("()");
|
||||
} else {
|
||||
sb.append("(").append(concat(params)).append(")");
|
||||
}
|
||||
loadJs(sb.toString(), callback);
|
||||
}
|
||||
|
||||
private String concat(String... params) {
|
||||
StringBuilder mStringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
String param = params[i];
|
||||
if (!isJson(param)) {
|
||||
mStringBuilder.append("\"").append(param).append("\"");
|
||||
} else {
|
||||
mStringBuilder.append(param);
|
||||
}
|
||||
if (i != params.length - 1) {
|
||||
mStringBuilder.append(" , ");
|
||||
}
|
||||
}
|
||||
return mStringBuilder.toString();
|
||||
}
|
||||
|
||||
public void quickCallJs(String method, String... params) {
|
||||
this.quickCallJs(method, null, params);
|
||||
}
|
||||
|
||||
public void quickCallJs(String method) {
|
||||
this.quickCallJs(method, (String[]) null);
|
||||
}
|
||||
|
||||
static boolean isJson(String target) {
|
||||
if (TextUtils.isEmpty(target)) {
|
||||
return false;
|
||||
}
|
||||
boolean tag = false;
|
||||
try {
|
||||
if (target.startsWith("[")) {
|
||||
new JSONArray(target);
|
||||
} else {
|
||||
new JSONObject(target);
|
||||
}
|
||||
tag = true;
|
||||
} catch (JSONException ignore) {
|
||||
// ignore.printStackTrace();
|
||||
tag = false;
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
}
|
359
app/src/main/java/me/jingbin/web/ByWebChromeClient.java
Normal file
@ -0,0 +1,359 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.JsPromptResult;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.PermissionRequest;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
|
||||
/**
|
||||
* Created by jingbin on 2019/07/27.
|
||||
* - 播放网络视频配置
|
||||
* - 上传图片(兼容)
|
||||
*/
|
||||
public class ByWebChromeClient extends WebChromeClient {
|
||||
|
||||
private WeakReference<Activity> mActivityWeakReference;
|
||||
private ByWebView mByWebView;
|
||||
private ValueCallback<Uri> mUploadMessage;
|
||||
private ValueCallback<Uri[]> mUploadMessageForAndroid5;
|
||||
private static final int RESULT_CODE_FILE_CHOOSER = 1;
|
||||
private static final int RESULT_CODE_FILE_CHOOSER_FOR_ANDROID_5 = 2;
|
||||
|
||||
private View mProgressVideo;
|
||||
private View mCustomView;
|
||||
private CustomViewCallback mCustomViewCallback;
|
||||
private ByFullscreenHolder videoFullView;
|
||||
private OnTitleProgressCallback onByWebChromeCallback;
|
||||
// 修复可能部分h5无故横屏问题
|
||||
private boolean isFixScreenLandscape = false;
|
||||
// 修复可能部分h5无故竖屏问题
|
||||
private boolean isFixScreenPortrait = false;
|
||||
|
||||
ByWebChromeClient(Activity activity, ByWebView byWebView) {
|
||||
mActivityWeakReference = new WeakReference<Activity>(activity);
|
||||
this.mByWebView = byWebView;
|
||||
}
|
||||
|
||||
void setOnByWebChromeCallback(OnTitleProgressCallback onByWebChromeCallback) {
|
||||
this.onByWebChromeCallback = onByWebChromeCallback;
|
||||
}
|
||||
|
||||
public void setFixScreenLandscape(boolean fixScreenLandscape) {
|
||||
isFixScreenLandscape = fixScreenLandscape;
|
||||
}
|
||||
|
||||
public void setFixScreenPortrait(boolean fixScreenPortrait) {
|
||||
isFixScreenPortrait = fixScreenPortrait;
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放网络视频时全屏会被调用的方法
|
||||
*/
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Override
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {
|
||||
Activity mActivity = this.mActivityWeakReference.get();
|
||||
if (mActivity != null && !mActivity.isFinishing()) {
|
||||
if (!isFixScreenLandscape) {
|
||||
if (onByWebChromeCallback == null || !onByWebChromeCallback.onHandleScreenOrientation(true)) {
|
||||
// 为空或返回为true时,自己处理横竖屏。否则全屏时默认设置为横屏
|
||||
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
}
|
||||
mByWebView.getWebView().setVisibility(View.INVISIBLE);
|
||||
|
||||
// 如果一个视图已经存在,那么立刻终止并新建一个
|
||||
if (mCustomView != null) {
|
||||
callback.onCustomViewHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
if (videoFullView == null) {
|
||||
FrameLayout decor = (FrameLayout) mActivity.getWindow().getDecorView();
|
||||
videoFullView = new ByFullscreenHolder(mActivity);
|
||||
decor.addView(videoFullView);
|
||||
}
|
||||
videoFullView.addView(view);
|
||||
|
||||
mCustomView = view;
|
||||
mCustomViewCallback = callback;
|
||||
videoFullView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频播放退出全屏会被调用的
|
||||
*/
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
Activity mActivity = this.mActivityWeakReference.get();
|
||||
if (mActivity != null && !mActivity.isFinishing()) {
|
||||
// 不是全屏播放状态
|
||||
if (mCustomView == null) {
|
||||
return;
|
||||
}
|
||||
// 还原到之前的屏幕状态
|
||||
if (!isFixScreenPortrait) {
|
||||
if (onByWebChromeCallback == null || !onByWebChromeCallback.onHandleScreenOrientation(false)) {
|
||||
// 为空或返回为true时,自己处理横竖屏。否则默认设置为竖屏
|
||||
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
}
|
||||
|
||||
mCustomView.setVisibility(View.GONE);
|
||||
if (videoFullView != null) {
|
||||
videoFullView.removeView(mCustomView);
|
||||
videoFullView.setVisibility(View.GONE);
|
||||
}
|
||||
mCustomView = null;
|
||||
mCustomViewCallback.onCustomViewHidden();
|
||||
mByWebView.getWebView().setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
super.onProgressChanged(view, newProgress);
|
||||
// 进度条
|
||||
if (mByWebView.getProgressBar() != null) {
|
||||
mByWebView.getProgressBar().setWebProgress(newProgress);
|
||||
}
|
||||
// 当显示错误页面时,进度达到100才显示网页
|
||||
if (mByWebView.getWebView() != null
|
||||
&& mByWebView.getWebView().getVisibility() == View.INVISIBLE
|
||||
&& (mByWebView.getErrorView() == null || mByWebView.getErrorView().getVisibility() == View.GONE)
|
||||
&& newProgress == 100) {
|
||||
mByWebView.getWebView().setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (onByWebChromeCallback != null) {
|
||||
onByWebChromeCallback.onProgressChanged(newProgress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是全屏
|
||||
*/
|
||||
boolean inCustomView() {
|
||||
return (mCustomView != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedTitle(WebView view, String title) {
|
||||
super.onReceivedTitle(view, title);
|
||||
// 设置title
|
||||
if (onByWebChromeCallback != null) {
|
||||
if (mByWebView.getErrorView() != null && mByWebView.getErrorView().getVisibility() == View.VISIBLE) {
|
||||
onByWebChromeCallback.onReceivedTitle(TextUtils.isEmpty(mByWebView.getErrorTitle()) ? "网页无法打开" : mByWebView.getErrorTitle());
|
||||
} else {
|
||||
onByWebChromeCallback.onReceivedTitle(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
||||
if (onByWebChromeCallback != null && onByWebChromeCallback.onJsAlert(view, url, message, result)) {
|
||||
return true;
|
||||
}
|
||||
Dialog alertDialog = new AlertDialog.Builder(view.getContext()).
|
||||
setMessage(message).
|
||||
setCancelable(false).
|
||||
setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
result.confirm();
|
||||
}
|
||||
})
|
||||
.create();
|
||||
alertDialog.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
||||
if (onByWebChromeCallback != null && onByWebChromeCallback.onJsConfirm(view, url, message, result)) {
|
||||
return true;
|
||||
}
|
||||
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == Dialog.BUTTON_POSITIVE) {
|
||||
result.confirm();
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
};
|
||||
new AlertDialog.Builder(view.getContext())
|
||||
.setMessage(message)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok, listener)
|
||||
.setNegativeButton(android.R.string.cancel, listener).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onJsPrompt(final WebView view, String url, final String message, final String defaultValue, final JsPromptResult result) {
|
||||
if (onByWebChromeCallback != null && onByWebChromeCallback.onJsPrompt(view, url, message, defaultValue, result)) {
|
||||
return true;
|
||||
}
|
||||
view.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final EditText editText = new EditText(view.getContext());
|
||||
editText.setText(defaultValue);
|
||||
if (defaultValue != null) {
|
||||
editText.setSelection(defaultValue.length());
|
||||
}
|
||||
float dpi = view.getContext().getResources().getDisplayMetrics().density;
|
||||
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == Dialog.BUTTON_POSITIVE) {
|
||||
result.confirm(editText.getText().toString());
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
};
|
||||
new AlertDialog.Builder(view.getContext())
|
||||
.setTitle(message)
|
||||
.setView(editText)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok, listener)
|
||||
.setNegativeButton(android.R.string.cancel, listener)
|
||||
.show();
|
||||
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
int t = (int) (dpi * 16);
|
||||
layoutParams.setMargins(t, 0, t, 0);
|
||||
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
editText.setLayoutParams(layoutParams);
|
||||
int padding = (int) (15 * dpi);
|
||||
editText.setPadding(padding - (int) (5 * dpi), padding, padding, padding);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
//扩展浏览器上传文件
|
||||
//3.0++版本
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
|
||||
openFileChooserImpl(uploadMsg);
|
||||
}
|
||||
|
||||
//3.0--版本
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
||||
openFileChooserImpl(uploadMsg);
|
||||
}
|
||||
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
|
||||
openFileChooserImpl(uploadMsg);
|
||||
}
|
||||
|
||||
// For Android > 5.0
|
||||
@Override
|
||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadMsg, FileChooserParams fileChooserParams) {
|
||||
openFileChooserImplForAndroid5(uploadMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
|
||||
Activity mActivity = this.mActivityWeakReference.get();
|
||||
if (mActivity != null && !mActivity.isFinishing()) {
|
||||
mUploadMessage = uploadMsg;
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("image/*");
|
||||
mActivity.startActivityForResult(Intent.createChooser(intent, "文件选择"), RESULT_CODE_FILE_CHOOSER);
|
||||
}
|
||||
}
|
||||
|
||||
private void openFileChooserImplForAndroid5(ValueCallback<Uri[]> uploadMsg) {
|
||||
Activity mActivity = this.mActivityWeakReference.get();
|
||||
if (mActivity != null && !mActivity.isFinishing()) {
|
||||
mUploadMessageForAndroid5 = uploadMsg;
|
||||
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
contentSelectionIntent.setType("image/*");
|
||||
|
||||
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
|
||||
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
|
||||
chooserIntent.putExtra(Intent.EXTRA_TITLE, "图片选择");
|
||||
|
||||
mActivity.startActivityForResult(chooserIntent, RESULT_CODE_FILE_CHOOSER_FOR_ANDROID_5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 5.0以下 上传图片成功后的回调
|
||||
*/
|
||||
private void uploadMessage(Intent intent, int resultCode) {
|
||||
if (null == mUploadMessage) {
|
||||
return;
|
||||
}
|
||||
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
||||
mUploadMessage.onReceiveValue(result);
|
||||
mUploadMessage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 5.0以上 上传图片成功后的回调
|
||||
*/
|
||||
private void uploadMessageForAndroid5(Intent intent, int resultCode) {
|
||||
if (null == mUploadMessageForAndroid5) {
|
||||
return;
|
||||
}
|
||||
Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null : intent.getData();
|
||||
if (result != null) {
|
||||
mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
|
||||
} else {
|
||||
mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
|
||||
}
|
||||
mUploadMessageForAndroid5 = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于Activity的回调
|
||||
*/
|
||||
public void handleFileChooser(int requestCode, int resultCode, Intent intent) {
|
||||
if (requestCode == RESULT_CODE_FILE_CHOOSER) {
|
||||
uploadMessage(intent, resultCode);
|
||||
} else if (requestCode == RESULT_CODE_FILE_CHOOSER_FOR_ANDROID_5) {
|
||||
uploadMessageForAndroid5(intent, resultCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionRequest(PermissionRequest request) {
|
||||
super.onPermissionRequest(request);
|
||||
// 部分页面可能崩溃
|
||||
// request.grant(request.getResources());
|
||||
}
|
||||
|
||||
ByFullscreenHolder getVideoFullView() {
|
||||
return videoFullView;
|
||||
}
|
||||
}
|
221
app/src/main/java/me/jingbin/web/ByWebTools.java
Normal file
@ -0,0 +1,221 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
import android.webkit.WebView;
|
||||
|
||||
/**
|
||||
* @author jingbin
|
||||
*/
|
||||
public class ByWebTools {
|
||||
|
||||
/**
|
||||
* 同步cookie,要放在loadUrl之前
|
||||
*
|
||||
* @param emptyKeys 空的键值对,用来清空cookie里的登录信息
|
||||
*/
|
||||
public static void syncCookie(WebView webView, String url, String cookies, String... emptyKeys) {
|
||||
if (!TextUtils.isEmpty(url)) {
|
||||
try {
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (cookieManager == null) {
|
||||
return;
|
||||
}
|
||||
cookieManager.setAcceptCookie(true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.setAcceptThirdPartyCookies(webView, true); //跨域cookie读取
|
||||
}
|
||||
if (!TextUtils.isEmpty(cookies)) {
|
||||
String[] split = cookies.split(";");
|
||||
for (String s : split) {
|
||||
cookieManager.setCookie(url, s);
|
||||
}
|
||||
if (emptyKeys != null && emptyKeys.length > 0) {
|
||||
for (String key : emptyKeys) {
|
||||
if (!TextUtils.isEmpty(key) && !cookies.contains(key)) {
|
||||
cookieManager.setCookie(url, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (emptyKeys != null && emptyKeys.length > 0) {
|
||||
for (String key : emptyKeys) {
|
||||
if (!TextUtils.isEmpty(key)) {
|
||||
cookieManager.setCookie(url, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
CookieManager.getInstance().flush();
|
||||
} else {
|
||||
CookieSyncManager.getInstance().sync();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Android5.0以下手机不能直接打开mp4后缀的链接
|
||||
*
|
||||
* @param url 视频链接
|
||||
*/
|
||||
static String getVideoHtmlBody(String url) {
|
||||
return "<html>" +
|
||||
"<head>" +
|
||||
"<meta name=\"viewport\" content=\"width=device-width\">" +
|
||||
"<style type=\"text/css\" abt=\"234\"></style>" +
|
||||
"</head>" +
|
||||
"<body>" +
|
||||
"<video controls=\"\" autoplay=\"\" name=\"media\" style=\"display:block;width:100%;position:absolute;left:0;top:20%;\">" +
|
||||
"<source src=\"" + url + "\" type=\"video/mp4\">" +
|
||||
"</video>" +
|
||||
"</body>" +
|
||||
"</html>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过包名找应用,不需要权限
|
||||
*/
|
||||
public static boolean hasPackage(Context context, String packageName) {
|
||||
if (null == context || TextUtils.isEmpty(packageName)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_GIDS);
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// 抛出找不到的异常,说明该程序已经被卸载
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认处理流程:网页里可能唤起其他的app
|
||||
*/
|
||||
public static boolean handleThirdApp(Activity activity, String backUrl) {
|
||||
/**http开头直接跳过*/
|
||||
if (backUrl.startsWith("http")) {
|
||||
// 可能有提示下载Apk文件
|
||||
if (backUrl.contains(".apk")) {
|
||||
startActivity(activity, backUrl);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (backUrl.contains("alipays")) {
|
||||
// 网页跳支付宝支付
|
||||
if (hasPackage(activity, "com.eg.android.AlipayGphone")) {
|
||||
startActivity(activity, backUrl);
|
||||
}
|
||||
|
||||
} else if (backUrl.contains("weixin://wap/pay")) {
|
||||
// 微信支付
|
||||
if (hasPackage(activity, "com.tencent.mm")) {
|
||||
startActivity(activity, backUrl);
|
||||
}
|
||||
} else {
|
||||
|
||||
// 会唤起手机里有的App,如果不想被唤起,复制出来然后添加屏蔽即可
|
||||
boolean isJump = true;
|
||||
if (backUrl.contains("tbopen:")// 淘宝
|
||||
|| backUrl.contains("openapp.jdmobile:")// 京东
|
||||
|| backUrl.contains("jdmobile:")//京东
|
||||
|| backUrl.contains("zhihu:")// 知乎
|
||||
|| backUrl.contains("vipshop:")//
|
||||
|| backUrl.contains("youku:")//优酷
|
||||
|| backUrl.contains("uclink:")// UC
|
||||
|| backUrl.contains("ucbrowser:")// UC
|
||||
|| backUrl.contains("newsapp:")//
|
||||
|| backUrl.contains("sinaweibo:")// 新浪微博
|
||||
|| backUrl.contains("suning:")//
|
||||
|| backUrl.contains("pinduoduo:")// 拼多多
|
||||
|| backUrl.contains("qtt:")//
|
||||
|| backUrl.contains("baiduboxapp:")// 百度
|
||||
|| backUrl.contains("baiduhaokan:")// 百度看看
|
||||
) {
|
||||
isJump = false;
|
||||
}
|
||||
if (isJump) {
|
||||
startActivity(activity, backUrl);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void startActivity(Context context, String url) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction("android.intent.action.VIEW");
|
||||
intent.setData(Uri.parse(url));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断网络是否连通
|
||||
*/
|
||||
static boolean isNetworkConnected(Context context) {
|
||||
try {
|
||||
if (context != null) {
|
||||
@SuppressWarnings("static-access")
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo info = cm.getActiveNetworkInfo();
|
||||
return info != null && info.isConnected();
|
||||
} else {
|
||||
/**如果context为空,就返回false,表示网络未连接*/
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static int dip2px(Context context, float dpValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
public static String getUrl(String url) {
|
||||
String urlResult = "";
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
// 空url
|
||||
return urlResult;
|
||||
|
||||
} else if (url.startsWith("http")) {
|
||||
// 直接返回
|
||||
return url;
|
||||
|
||||
} else if (url.contains("http")) {
|
||||
// 有http且不在头部
|
||||
urlResult = url.substring(url.indexOf("http"));
|
||||
|
||||
} else if (url.startsWith("www")) {
|
||||
// 以"www"开头
|
||||
urlResult = "http://" + url;
|
||||
|
||||
} else if ((url.contains(".me") || url.contains(".com") || url.contains(".cn"))) {
|
||||
// 不以"http"开头且有后缀
|
||||
urlResult = "http://www." + url;
|
||||
|
||||
} else if (!url.contains("www")) {
|
||||
// 输入纯文字的情况
|
||||
urlResult = "http://m5.baidu.com/s?from=124n&word=" + url;
|
||||
}
|
||||
return urlResult;
|
||||
}
|
||||
}
|
475
app/src/main/java/me/jingbin/web/ByWebView.java
Normal file
@ -0,0 +1,475 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* 网页可以处理:
|
||||
* 点击相应控件:
|
||||
* - 进度条显示
|
||||
* - 上传图片(版本兼容)
|
||||
* - 全屏播放网络视频
|
||||
* - 唤起微信支付宝
|
||||
* - 拨打电话、发送短信、发送邮件
|
||||
* - 返回网页上一层、显示网页标题
|
||||
* JS交互部分:
|
||||
* - 前端代码嵌入js(缺乏灵活性)
|
||||
* - 网页自带js跳转
|
||||
*
|
||||
* @author jingbin
|
||||
* link to https://github.com/youlookwhat/ByWebView
|
||||
*/
|
||||
public class ByWebView {
|
||||
|
||||
private WebView mWebView;
|
||||
private WebProgress mProgressBar;
|
||||
private View mErrorView;
|
||||
private int mErrorLayoutId;
|
||||
private String mErrorTitle;
|
||||
private Activity activity;
|
||||
private ByWebChromeClient mWebChromeClient;
|
||||
private ByLoadJsHolder byLoadJsHolder;
|
||||
|
||||
private ByWebView(Builder builder) {
|
||||
this.activity = builder.mActivity;
|
||||
this.mErrorTitle = builder.mErrorTitle;
|
||||
this.mErrorLayoutId = builder.mErrorLayoutId;
|
||||
|
||||
FrameLayout parentLayout = new FrameLayout(activity);
|
||||
// 设置WebView
|
||||
setWebView(builder.mCustomWebView);
|
||||
parentLayout.addView(mWebView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
// 进度条布局
|
||||
handleWebProgress(builder, parentLayout);
|
||||
if (builder.mIndex != -1) {
|
||||
builder.mWebContainer.addView(parentLayout, builder.mIndex, builder.mLayoutParams);
|
||||
} else {
|
||||
builder.mWebContainer.addView(parentLayout, builder.mLayoutParams);
|
||||
}
|
||||
// 配置
|
||||
handleSetting();
|
||||
// 视频、照片、进度条
|
||||
mWebChromeClient = new ByWebChromeClient(activity, this);
|
||||
mWebChromeClient.setOnByWebChromeCallback(builder.mOnTitleProgressCallback);
|
||||
mWebView.setWebChromeClient(mWebChromeClient);
|
||||
|
||||
// 错误页面、页面结束、处理DeepLink
|
||||
ByWebViewClient mByWebViewClient = new ByWebViewClient(activity, this);
|
||||
mByWebViewClient.setOnByWebClientCallback(builder.mOnByWebClientCallback);
|
||||
mWebView.setWebViewClient(mByWebViewClient);
|
||||
|
||||
handleJsInterface(builder);
|
||||
|
||||
// 移除有风险的WebView系统隐藏接口
|
||||
mWebView.removeJavascriptInterface("searchBoxJavaBridge_");
|
||||
mWebView.removeJavascriptInterface("accessibility");
|
||||
mWebView.removeJavascriptInterface("accessibilityTraversal");
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置自定义的WebView
|
||||
*/
|
||||
private void setWebView(WebView mCustomWebView) {
|
||||
if (mCustomWebView != null) {
|
||||
mWebView = mCustomWebView;
|
||||
} else {
|
||||
mWebView = new WebView(activity);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint({"JavascriptInterface", "AddJavascriptInterface"})
|
||||
private void handleJsInterface(Builder builder) {
|
||||
if (!TextUtils.isEmpty(builder.mInterfaceName) && builder.mInterfaceObj != null) {
|
||||
mWebView.addJavascriptInterface(builder.mInterfaceObj, builder.mInterfaceName);
|
||||
}
|
||||
}
|
||||
|
||||
public ByLoadJsHolder getLoadJsHolder() {
|
||||
if (byLoadJsHolder == null) {
|
||||
byLoadJsHolder = new ByLoadJsHolder(mWebView);
|
||||
}
|
||||
return byLoadJsHolder;
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private void handleSetting() {
|
||||
WebSettings ws = mWebView.getSettings();
|
||||
// 保存表单数据
|
||||
ws.setSaveFormData(true);
|
||||
// 是否应该支持使用其屏幕缩放控件和手势缩放
|
||||
ws.setSupportZoom(true);
|
||||
ws.setBuiltInZoomControls(true);
|
||||
ws.setDisplayZoomControls(false);
|
||||
// 设置缓存模式
|
||||
ws.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
// setDefaultZoom api19被弃用
|
||||
// 网页内容的宽度自适应屏幕
|
||||
ws.setLoadWithOverviewMode(true);
|
||||
ws.setUseWideViewPort(true);
|
||||
// 告诉WebView启用JavaScript执行。默认的是false。
|
||||
ws.setJavaScriptEnabled(true);
|
||||
// 页面加载好以后,再放开图片
|
||||
ws.setBlockNetworkImage(false);
|
||||
// 使用localStorage则必须打开
|
||||
ws.setDomStorageEnabled(true);
|
||||
// 排版适应屏幕
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
ws.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
|
||||
} else {
|
||||
ws.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
|
||||
}
|
||||
// WebView是否新窗口打开(加了后可能打不开网页)
|
||||
// ws.setSupportMultipleWindows(true);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// WebView从5.0开始默认不允许混合模式,https中不能加载http资源,需要设置开启。
|
||||
ws.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字体默认缩放大小(改变网页字体大小,setTextSize api14被弃用)
|
||||
*
|
||||
* @param textZoom 默认100
|
||||
*/
|
||||
public void setTextZoom(int textZoom) {
|
||||
mWebView.getSettings().setTextZoom(textZoom);
|
||||
}
|
||||
|
||||
private void handleWebProgress(Builder builder, FrameLayout parentLayout) {
|
||||
if (builder.mUseWebProgress) {
|
||||
mProgressBar = new WebProgress(activity);
|
||||
if (builder.mProgressStartColor != 0 && builder.mProgressEndColor != 0) {
|
||||
mProgressBar.setColor(builder.mProgressStartColor, builder.mProgressEndColor);
|
||||
} else if (builder.mProgressStartColor != 0) {
|
||||
mProgressBar.setColor(builder.mProgressStartColor, builder.mProgressStartColor);
|
||||
} else if (!TextUtils.isEmpty(builder.mProgressStartColorString)
|
||||
&& !TextUtils.isEmpty(builder.mProgressEndColorString)) {
|
||||
mProgressBar.setColor(builder.mProgressStartColorString, builder.mProgressEndColorString);
|
||||
} else if (!TextUtils.isEmpty(builder.mProgressStartColorString)
|
||||
&& TextUtils.isEmpty(builder.mProgressEndColorString)) {
|
||||
mProgressBar.setColor(builder.mProgressStartColorString, builder.mProgressStartColorString);
|
||||
}
|
||||
int progressHeight = ByWebTools.dip2px(parentLayout.getContext(), WebProgress.WEB_PROGRESS_DEFAULT_HEIGHT);
|
||||
if (builder.mProgressHeightDp != 0) {
|
||||
mProgressBar.setHeight(builder.mProgressHeightDp);
|
||||
progressHeight = ByWebTools.dip2px(parentLayout.getContext(), builder.mProgressHeightDp);
|
||||
}
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
parentLayout.addView(mProgressBar, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, progressHeight));
|
||||
}
|
||||
}
|
||||
|
||||
public void loadUrl(String url) {
|
||||
if (!TextUtils.isEmpty(url) && url.endsWith("mp4") && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
mWebView.loadData(ByWebTools.getVideoHtmlBody(url), "text/html", "UTF-8");
|
||||
} else {
|
||||
mWebView.loadUrl(url);
|
||||
}
|
||||
if (mProgressBar != null) {
|
||||
mProgressBar.show();
|
||||
}
|
||||
hideErrorView();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
hideErrorView();
|
||||
mWebView.reload();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
mWebView.onResume();
|
||||
// 支付宝网页版在打开文章详情之后,无法点击按钮下一步
|
||||
mWebView.resumeTimers();
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
mWebView.onPause();
|
||||
mWebView.pauseTimers();
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
if (mWebChromeClient != null && mWebChromeClient.getVideoFullView() != null) {
|
||||
mWebChromeClient.getVideoFullView().removeAllViews();
|
||||
}
|
||||
if (mWebView != null) {
|
||||
ViewGroup parent = (ViewGroup) mWebView.getParent();
|
||||
if (parent != null) {
|
||||
parent.removeView(mWebView);
|
||||
}
|
||||
mWebView.removeAllViews();
|
||||
mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
|
||||
mWebView.stopLoading();
|
||||
mWebView.setWebChromeClient(null);
|
||||
mWebView.setWebViewClient(null);
|
||||
mWebView.destroy();
|
||||
mWebView = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择图片之后的回调,在Activity里onActivityResult调用
|
||||
*/
|
||||
public void handleFileChooser(int requestCode, int resultCode, Intent intent) {
|
||||
if (mWebChromeClient != null) {
|
||||
mWebChromeClient.handleFileChooser(requestCode, resultCode, intent);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handleKeyEvent(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return isBack();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
public boolean isBack() {
|
||||
// 全屏播放退出全屏
|
||||
if (mWebChromeClient.inCustomView()) {
|
||||
mWebChromeClient.onHideCustomView();
|
||||
return true;
|
||||
|
||||
// 返回网页上一页
|
||||
} else if (mWebView.canGoBack()) {
|
||||
hideErrorView();
|
||||
mWebView.goBack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public WebView getWebView() {
|
||||
return mWebView;
|
||||
}
|
||||
|
||||
public WebProgress getProgressBar() {
|
||||
return mProgressBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误布局
|
||||
*/
|
||||
public void showErrorView() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏错误布局
|
||||
*/
|
||||
public void hideErrorView() {
|
||||
if (mErrorView != null) {
|
||||
mErrorView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public View getErrorView() {
|
||||
return mErrorView;
|
||||
}
|
||||
|
||||
String getErrorTitle() {
|
||||
return mErrorTitle;
|
||||
}
|
||||
|
||||
public static Builder with(@NonNull Activity activity) {
|
||||
if (activity == null) {
|
||||
throw new NullPointerException("activity can not be null .");
|
||||
}
|
||||
return new Builder(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复可能部分h5无故竖屏问题,如果h5里有视频全屏播放请禁用
|
||||
*/
|
||||
public void setFixScreenPortrait(boolean fixScreenPortrait) {
|
||||
if (mWebChromeClient != null) {
|
||||
mWebChromeClient.setFixScreenPortrait(fixScreenPortrait);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复可能部分h5无故横屏问题,如果h5里有视频全屏播放请禁用
|
||||
*/
|
||||
public void setFixScreenLandscape(boolean fixScreenLandscape) {
|
||||
if (mWebChromeClient != null) {
|
||||
mWebChromeClient.setFixScreenLandscape(fixScreenLandscape);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Activity mActivity;
|
||||
// 默认使用进度条
|
||||
private boolean mUseWebProgress = true;
|
||||
// 进度条 开始颜色
|
||||
private int mProgressStartColor;
|
||||
private String mProgressStartColorString;
|
||||
// 进度条 结束颜色
|
||||
private int mProgressEndColor;
|
||||
private String mProgressEndColorString;
|
||||
// 进度条 高度
|
||||
private int mProgressHeightDp;
|
||||
private int mErrorLayoutId;
|
||||
private int mIndex = -1;
|
||||
private String mErrorTitle;
|
||||
private WebView mCustomWebView;
|
||||
private String mInterfaceName;
|
||||
private Object mInterfaceObj;
|
||||
private ViewGroup mWebContainer;
|
||||
private ViewGroup.LayoutParams mLayoutParams;
|
||||
private OnTitleProgressCallback mOnTitleProgressCallback;
|
||||
private OnByWebClientCallback mOnByWebClientCallback;
|
||||
|
||||
public Builder(Activity activity) {
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebView容器
|
||||
*/
|
||||
public Builder setWebParent(@NonNull ViewGroup webContainer, ViewGroup.LayoutParams layoutParams) {
|
||||
this.mWebContainer = webContainer;
|
||||
this.mLayoutParams = layoutParams;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebView容器
|
||||
*
|
||||
* @param webContainer 外部WebView容器
|
||||
* @param index 加入的位置
|
||||
* @param layoutParams 对应的LayoutParams
|
||||
*/
|
||||
public Builder setWebParent(@NonNull ViewGroup webContainer, int index, ViewGroup.LayoutParams layoutParams) {
|
||||
this.mWebContainer = webContainer;
|
||||
this.mIndex = index;
|
||||
this.mLayoutParams = layoutParams;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isUse 是否使用进度条,默认true
|
||||
*/
|
||||
public Builder useWebProgress(boolean isUse) {
|
||||
this.mUseWebProgress = isUse;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度条颜色
|
||||
*
|
||||
* @param color 示例:ContextCompat.getColor(this, R.color.red)
|
||||
*/
|
||||
public Builder useWebProgress(int color) {
|
||||
return useWebProgress(color, color, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度条颜色
|
||||
*
|
||||
* @param color 示例:"#FF0000"
|
||||
*/
|
||||
public Builder useWebProgress(String color) {
|
||||
return useWebProgress(color, color, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度条渐变色颜色
|
||||
*
|
||||
* @param startColor 开始颜色
|
||||
* @param endColor 结束颜色
|
||||
* @param heightDp 进度条高度,单位dp
|
||||
*/
|
||||
public Builder useWebProgress(int startColor, int endColor, int heightDp) {
|
||||
mProgressStartColor = startColor;
|
||||
mProgressEndColor = endColor;
|
||||
mProgressHeightDp = heightDp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder useWebProgress(String startColor, String endColor, int heightDp) {
|
||||
mProgressStartColorString = startColor;
|
||||
mProgressEndColorString = endColor;
|
||||
mProgressHeightDp = heightDp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param customWebView 自定义的WebView
|
||||
*/
|
||||
public Builder setCustomWebView(WebView customWebView) {
|
||||
mCustomWebView = customWebView;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorLayoutId 错误页面布局,标题默认“网页打开失败”
|
||||
*/
|
||||
public Builder setErrorLayout(@LayoutRes int errorLayoutId) {
|
||||
mErrorLayoutId = errorLayoutId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorLayoutId 错误页面布局
|
||||
* @param errorTitle 错误页面标题
|
||||
*/
|
||||
public Builder setErrorLayout(@LayoutRes int errorLayoutId, String errorTitle) {
|
||||
mErrorLayoutId = errorLayoutId;
|
||||
mErrorTitle = errorTitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Js监听
|
||||
*/
|
||||
public Builder addJavascriptInterface(String interfaceName, Object interfaceObj) {
|
||||
this.mInterfaceName = interfaceName;
|
||||
this.mInterfaceObj = interfaceObj;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onTitleProgressCallback 返回Title 、 Progress ,横竖屏和弹框
|
||||
*/
|
||||
public Builder setOnTitleProgressCallback(OnTitleProgressCallback onTitleProgressCallback) {
|
||||
this.mOnTitleProgressCallback = onTitleProgressCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面加载结束监听 和 处理三方跳转链接
|
||||
*/
|
||||
public Builder setOnByWebClientCallback(OnByWebClientCallback onByWebClientCallback) {
|
||||
this.mOnByWebClientCallback = onByWebClientCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接获取ByWebView,避免一定要调用loadUrl()才能获取ByWebView的情况
|
||||
*/
|
||||
public ByWebView get() {
|
||||
return new ByWebView(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* loadUrl()并获取ByWebView
|
||||
*/
|
||||
public ByWebView loadUrl(String url) {
|
||||
ByWebView byWebView = get();
|
||||
byWebView.loadUrl(url);
|
||||
return byWebView;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
172
app/src/main/java/me/jingbin/web/ByWebViewClient.java
Normal file
@ -0,0 +1,172 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebResourceError;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Created by jingbin on 2020/06/30
|
||||
* 监听网页链接:
|
||||
* - 根据标识:打电话、发短信、发邮件
|
||||
* - 进度条的显示
|
||||
* - 添加javascript监听
|
||||
* - 唤起京东,支付宝,微信原生App
|
||||
*/
|
||||
public class ByWebViewClient extends WebViewClient {
|
||||
|
||||
private WeakReference<Activity> mActivityWeakReference = null;
|
||||
private ByWebView mByWebView;
|
||||
private OnByWebClientCallback onByWebClientCallback;
|
||||
|
||||
ByWebViewClient(Activity activity, ByWebView byWebView) {
|
||||
mActivityWeakReference = new WeakReference<Activity>(activity);
|
||||
this.mByWebView = byWebView;
|
||||
}
|
||||
|
||||
void setOnByWebClientCallback(OnByWebClientCallback onByWebClientCallback) {
|
||||
this.onByWebClientCallback = onByWebClientCallback;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
String url = request.getUrl().toString();
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
return false;
|
||||
}
|
||||
if (onByWebClientCallback != null) {
|
||||
return onByWebClientCallback.isOpenThirdApp(url);
|
||||
} else {
|
||||
Activity mActivity = this.mActivityWeakReference.get();
|
||||
if (mActivity != null && !mActivity.isFinishing()) {
|
||||
return ByWebTools.handleThirdApp(mActivity, url);
|
||||
} else {
|
||||
return !url.startsWith("http:") && !url.startsWith("https:");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
return false;
|
||||
}
|
||||
if (onByWebClientCallback != null) {
|
||||
return onByWebClientCallback.isOpenThirdApp(url);
|
||||
} else {
|
||||
Activity mActivity = this.mActivityWeakReference.get();
|
||||
if (mActivity != null && !mActivity.isFinishing()) {
|
||||
return ByWebTools.handleThirdApp(mActivity, url);
|
||||
} else {
|
||||
return !url.startsWith("http:") && !url.startsWith("https:");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
if (onByWebClientCallback != null) {
|
||||
onByWebClientCallback.onPageStarted(view, url, favicon);
|
||||
}
|
||||
super.onPageStarted(view, url, favicon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
// html加载完成之后,添加监听图片的点击js函数
|
||||
Activity mActivity = this.mActivityWeakReference.get();
|
||||
if (mActivity != null && !mActivity.isFinishing()
|
||||
&& !ByWebTools.isNetworkConnected(mActivity) && mByWebView.getProgressBar() != null) {
|
||||
mByWebView.getProgressBar().hide();
|
||||
}
|
||||
if (onByWebClientCallback != null) {
|
||||
onByWebClientCallback.onPageFinished(view, url);
|
||||
}
|
||||
super.onPageFinished(view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||
// 6.0以下执行
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return;
|
||||
}
|
||||
mByWebView.showErrorView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
|
||||
super.onReceivedHttpError(view, request, errorResponse);
|
||||
// 这个方法在 android 6.0才出现。加了正常的页面可能会出现错误页面
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// int statusCode = errorResponse.getStatusCode();
|
||||
// if (404 == statusCode || 500 == statusCode) {
|
||||
// mByWebView.showErrorView();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||
super.onReceivedError(view, request, error);
|
||||
if (request.isForMainFrame()) {
|
||||
// 是否是为 main frame创建
|
||||
mByWebView.showErrorView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决google play上线 WebViewClient.onReceivedSslError问题
|
||||
*/
|
||||
@Override
|
||||
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
|
||||
if (onByWebClientCallback == null || !onByWebClientCallback.onReceivedSslError(view, handler, error)) {
|
||||
// 默认http请求会有弹框提示,如果要自己处理需要使用onReceivedSslError()且返回true
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
|
||||
builder.setMessage("SSL认证失败,是否继续访问?");
|
||||
builder.setPositiveButton("继续", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
handler.proceed();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
handler.cancel();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频全屏播放按返回页面被放大的问题
|
||||
*/
|
||||
@Override
|
||||
public void onScaleChanged(WebView view, float oldScale, float newScale) {
|
||||
super.onScaleChanged(view, oldScale, newScale);
|
||||
if (newScale - oldScale > 7) {
|
||||
//异常放大,缩回去。
|
||||
view.setInitialScale((int) (oldScale / newScale * 100));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
31
app/src/main/java/me/jingbin/web/OnByWebClientCallback.java
Normal file
@ -0,0 +1,31 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.http.SslError;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebView;
|
||||
|
||||
/**
|
||||
* Created by jingbin on 2020/6/30.
|
||||
*/
|
||||
public abstract class OnByWebClientCallback {
|
||||
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
|
||||
}
|
||||
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
|
||||
}
|
||||
|
||||
public boolean isOpenThirdApp(String url) {
|
||||
return !url.startsWith("http:") && !url.startsWith("https:");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true 表示是自己处理的
|
||||
*/
|
||||
public boolean onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.webkit.JsPromptResult;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.WebView;
|
||||
|
||||
/**
|
||||
* Created by jingbin on 2020/6/30.
|
||||
*/
|
||||
public abstract class OnTitleProgressCallback {
|
||||
|
||||
/**
|
||||
* @param title 返回的标题
|
||||
*/
|
||||
public void onReceivedTitle(String title) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newProgress 返回的进度
|
||||
*/
|
||||
public void onProgressChanged(int newProgress) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 全屏显示时处理横竖屏。
|
||||
* 默认返回false,全屏时为横屏,全屏还原后为竖屏
|
||||
* 如果要手动处理,需要返回true!
|
||||
*
|
||||
* @param isShow 是否显示了全屏视频 true点击了全屏显示,false全屏视频还原
|
||||
*/
|
||||
public boolean onHandleScreenOrientation(boolean isShow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript alert 警告框
|
||||
* 默认返回false,使用代码里的默认处理
|
||||
* 如果要手动处理,需要返回true!且需要执行 result.confirm();
|
||||
*/
|
||||
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript confirm 确认框
|
||||
* 默认返回false,使用代码里的默认处理
|
||||
* 如果要手动处理,需要返回true!且需要执行 result.confirm();
|
||||
*/
|
||||
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript prompt 提示框
|
||||
* 默认返回false,使用代码里的默认处理
|
||||
* 如果要手动处理,需要返回true!且需要执行 result.confirm();
|
||||
*/
|
||||
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
|
||||
return false;
|
||||
}
|
||||
}
|
369
app/src/main/java/me/jingbin/web/WebProgress.java
Normal file
@ -0,0 +1,369 @@
|
||||
package me.jingbin.web;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* WebView进度条,原作者: cenxiaozhong,在此基础上修改优化:
|
||||
* 1. progress同时返回两次100时进度条出现两次
|
||||
* 2. 当一条进度没跑完,又点击其他链接开始第二次进度时,第二次进度不出现
|
||||
* 3. 修改消失动画时长,使其消失时看到可以进度跑完
|
||||
* 4. [2019.9.29] 修复当第一次进度返回 0 或超过 10,出现不显示进度条的问题
|
||||
* 5. 能显示渐变色
|
||||
* 6. 进度在95-100时再次开始进度条透明度问题
|
||||
*
|
||||
* @author jingbin
|
||||
* Link to https://github.com/youlookwhat/WebProgress
|
||||
*/
|
||||
public class WebProgress extends FrameLayout {
|
||||
|
||||
/**
|
||||
* 默认匀速动画最大的时长
|
||||
*/
|
||||
public static final int MAX_UNIFORM_SPEED_DURATION = 8 * 1000;
|
||||
/**
|
||||
* 默认加速后减速动画最大时长
|
||||
*/
|
||||
public static final int MAX_DECELERATE_SPEED_DURATION = 450;
|
||||
/**
|
||||
* 95f-100f时,透明度1f-0f时长
|
||||
*/
|
||||
public static final int DO_END_ALPHA_DURATION = 630;
|
||||
/**
|
||||
* 95f - 100f动画时长
|
||||
*/
|
||||
public static final int DO_END_PROGRESS_DURATION = 500;
|
||||
/**
|
||||
* 当前匀速动画最大的时长
|
||||
*/
|
||||
private static int CURRENT_MAX_UNIFORM_SPEED_DURATION = MAX_UNIFORM_SPEED_DURATION;
|
||||
/**
|
||||
* 当前加速后减速动画最大时长
|
||||
*/
|
||||
private static int CURRENT_MAX_DECELERATE_SPEED_DURATION = MAX_DECELERATE_SPEED_DURATION;
|
||||
/**
|
||||
* 默认的高度(dp)
|
||||
*/
|
||||
public static int WEB_PROGRESS_DEFAULT_HEIGHT = 3;
|
||||
/**
|
||||
* 进度条颜色默认
|
||||
*/
|
||||
public static String WEB_PROGRESS_COLOR = "#32B848";
|
||||
/**
|
||||
* 进度条颜色
|
||||
*/
|
||||
private int mColor;
|
||||
/**
|
||||
* 进度条的画笔
|
||||
*/
|
||||
private Paint mPaint;
|
||||
/**
|
||||
* 进度条动画
|
||||
*/
|
||||
private Animator mAnimator;
|
||||
/**
|
||||
* 控件的宽度
|
||||
*/
|
||||
private int mTargetWidth = 0;
|
||||
/**
|
||||
* 控件的高度
|
||||
*/
|
||||
private int mTargetHeight;
|
||||
/**
|
||||
* 标志当前进度条的状态
|
||||
*/
|
||||
private int TAG = 0;
|
||||
/**
|
||||
* 第一次过来进度show,后面就是setProgress
|
||||
*/
|
||||
private boolean isShow = false;
|
||||
public static final int UN_START = 0;
|
||||
public static final int STARTED = 1;
|
||||
public static final int FINISH = 2;
|
||||
private float mCurrentProgress = 0F;
|
||||
|
||||
public WebProgress(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public WebProgress(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public WebProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
mPaint = new Paint();
|
||||
mColor = Color.parseColor(WEB_PROGRESS_COLOR);
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setColor(mColor);
|
||||
mPaint.setDither(true);
|
||||
mPaint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
|
||||
mTargetWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
mTargetHeight = dip2px(WEB_PROGRESS_DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单色进度条
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
this.mColor = color;
|
||||
mPaint.setColor(color);
|
||||
}
|
||||
|
||||
public void setColor(String color) {
|
||||
this.setColor(Color.parseColor(color));
|
||||
}
|
||||
|
||||
public void setColor(int startColor, int endColor) {
|
||||
LinearGradient linearGradient = new LinearGradient(0, 0, mTargetWidth, mTargetHeight, startColor, endColor, Shader.TileMode.CLAMP);
|
||||
mPaint.setShader(linearGradient);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置渐变色进度条
|
||||
*
|
||||
* @param startColor 开始颜色
|
||||
* @param endColor 结束颜色
|
||||
*/
|
||||
public void setColor(String startColor, String endColor) {
|
||||
this.setColor(Color.parseColor(startColor), Color.parseColor(endColor));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
|
||||
int wMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int w = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int hMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int h = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
if (wMode == MeasureSpec.AT_MOST) {
|
||||
w = Math.min(w, getContext().getResources().getDisplayMetrics().widthPixels);
|
||||
}
|
||||
if (hMode == MeasureSpec.AT_MOST) {
|
||||
h = mTargetHeight;
|
||||
}
|
||||
this.setMeasuredDimension(w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
canvas.drawRect(0, 0, mCurrentProgress / 100 * (float) this.getWidth(), this.getHeight(), mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
this.mTargetWidth = getMeasuredWidth();
|
||||
int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
|
||||
if (mTargetWidth >= screenWidth) {
|
||||
CURRENT_MAX_DECELERATE_SPEED_DURATION = MAX_DECELERATE_SPEED_DURATION;
|
||||
CURRENT_MAX_UNIFORM_SPEED_DURATION = MAX_UNIFORM_SPEED_DURATION;
|
||||
} else {
|
||||
//取比值
|
||||
float rate = this.mTargetWidth / (float) screenWidth;
|
||||
CURRENT_MAX_UNIFORM_SPEED_DURATION = (int) (MAX_UNIFORM_SPEED_DURATION * rate);
|
||||
CURRENT_MAX_DECELERATE_SPEED_DURATION = (int) (MAX_DECELERATE_SPEED_DURATION * rate);
|
||||
}
|
||||
}
|
||||
|
||||
private void setFinish() {
|
||||
isShow = false;
|
||||
TAG = FINISH;
|
||||
}
|
||||
|
||||
private void startAnim(boolean isFinished) {
|
||||
|
||||
float v = isFinished ? 100 : 95;
|
||||
|
||||
if (mAnimator != null && mAnimator.isStarted()) {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
mCurrentProgress = mCurrentProgress == 0 ? 0.00000001f : mCurrentProgress;
|
||||
// 可能由于透明度造成突然出现的问题
|
||||
setAlpha(1);
|
||||
|
||||
if (!isFinished) {
|
||||
ValueAnimator mAnimator = ValueAnimator.ofFloat(mCurrentProgress, v);
|
||||
float residue = 1f - mCurrentProgress / 100 - 0.05f;
|
||||
mAnimator.setInterpolator(new LinearInterpolator());
|
||||
mAnimator.setDuration((long) (residue * CURRENT_MAX_UNIFORM_SPEED_DURATION));
|
||||
mAnimator.addUpdateListener(mAnimatorUpdateListener);
|
||||
mAnimator.start();
|
||||
this.mAnimator = mAnimator;
|
||||
} else {
|
||||
|
||||
ValueAnimator segment95Animator = null;
|
||||
if (mCurrentProgress < 95) {
|
||||
segment95Animator = ValueAnimator.ofFloat(mCurrentProgress, 95);
|
||||
float residue = 1f - mCurrentProgress / 100f - 0.05f;
|
||||
segment95Animator.setInterpolator(new LinearInterpolator());
|
||||
segment95Animator.setDuration((long) (residue * CURRENT_MAX_DECELERATE_SPEED_DURATION));
|
||||
segment95Animator.setInterpolator(new DecelerateInterpolator());
|
||||
segment95Animator.addUpdateListener(mAnimatorUpdateListener);
|
||||
}
|
||||
|
||||
ObjectAnimator mObjectAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f);
|
||||
mObjectAnimator.setDuration(DO_END_ALPHA_DURATION);
|
||||
ValueAnimator mValueAnimatorEnd = ValueAnimator.ofFloat(95f, 100f);
|
||||
mValueAnimatorEnd.setDuration(DO_END_PROGRESS_DURATION);
|
||||
mValueAnimatorEnd.addUpdateListener(mAnimatorUpdateListener);
|
||||
|
||||
AnimatorSet mAnimatorSet = new AnimatorSet();
|
||||
mAnimatorSet.playTogether(mObjectAnimator, mValueAnimatorEnd);
|
||||
|
||||
if (segment95Animator != null) {
|
||||
AnimatorSet mAnimatorSet1 = new AnimatorSet();
|
||||
mAnimatorSet1.play(mAnimatorSet).after(segment95Animator);
|
||||
mAnimatorSet = mAnimatorSet1;
|
||||
}
|
||||
mAnimatorSet.addListener(mAnimatorListenerAdapter);
|
||||
mAnimatorSet.start();
|
||||
mAnimator = mAnimatorSet;
|
||||
}
|
||||
|
||||
TAG = STARTED;
|
||||
}
|
||||
|
||||
private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
float t = (float) animation.getAnimatedValue();
|
||||
WebProgress.this.mCurrentProgress = t;
|
||||
WebProgress.this.invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
doEnd();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
/**
|
||||
* animator cause leak , if not cancel;
|
||||
*/
|
||||
if (mAnimator != null && mAnimator.isStarted()) {
|
||||
mAnimator.cancel();
|
||||
mAnimator = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void doEnd() {
|
||||
if (TAG == FINISH && mCurrentProgress == 100) {
|
||||
setVisibility(GONE);
|
||||
mCurrentProgress = 0f;
|
||||
this.setAlpha(1f);
|
||||
}
|
||||
TAG = UN_START;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mCurrentProgress = 0;
|
||||
if (mAnimator != null && mAnimator.isStarted()) {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int newProgress) {
|
||||
setProgress(Float.valueOf(newProgress));
|
||||
}
|
||||
|
||||
|
||||
public LayoutParams offerLayoutParams() {
|
||||
return new LayoutParams(mTargetWidth, mTargetHeight);
|
||||
}
|
||||
|
||||
private int dip2px(float dpValue) {
|
||||
final float scale = getContext().getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
public WebProgress setHeight(int heightDp) {
|
||||
this.mTargetHeight = dip2px(heightDp);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
// fix 同时返回两个 100,产生两次进度条的问题;
|
||||
if (TAG == UN_START && progress == 100) {
|
||||
setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getVisibility() == View.GONE) {
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (progress < 95) {
|
||||
return;
|
||||
}
|
||||
if (TAG != FINISH) {
|
||||
startAnim(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示进度条
|
||||
*/
|
||||
public void show() {
|
||||
isShow = true;
|
||||
setVisibility(View.VISIBLE);
|
||||
mCurrentProgress = 0f;
|
||||
startAnim(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度完成后消失
|
||||
*/
|
||||
public void hide() {
|
||||
setWebProgress(100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单独处理WebView进度条
|
||||
*/
|
||||
public void setWebProgress(int newProgress) {
|
||||
if (newProgress >= 0 && newProgress < 95) {
|
||||
if (!isShow) {
|
||||
show();
|
||||
} else {
|
||||
setProgress(newProgress);
|
||||
}
|
||||
} else {
|
||||
setProgress(newProgress);
|
||||
setFinish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
app/src/main/res/drawable-xxhdpi/actionbar_more.png
Normal file
After Width: | Height: | Size: 222 B |
BIN
app/src/main/res/drawable-xxhdpi/icon_back.png
Normal file
After Width: | Height: | Size: 887 B |
34
app/src/main/res/layout/activity_by_webview.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/ll_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="com.example.jingbin.webviewstudy.ui.ByWebViewActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/title_tool_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/colorPrimary"
|
||||
app:contentInsetStartWithNavigation="0dp"
|
||||
app:navigationContentDescription="返回"
|
||||
app:navigationIcon="@drawable/icon_back"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
app:theme="@style/ToolbarStyle">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_gun_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorWhite"
|
||||
android:textSize="18sp"
|
||||
android:visibility="visible" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</LinearLayout>
|
||||
|
33
app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/activity_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="com.paigramteam.nomihoyoapp.MainActivity">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/bt_openUrl"
|
||||
style="@style/Widget.AppCompat.Button.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="5dp"
|
||||
android:text="@string/bt_open_url" />
|
||||
|
||||
<View
|
||||
style="@style/lineStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
17
app/src/main/res/menu/menu_webview.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/actionbar_cope"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/actionbar_webview_cope"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/actionbar_webview_refresh"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/actionbar_webview_refresh"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
7
app/src/main/res/values-en/strings.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
<string name="app_name">NoMihoyoApp</string>
|
||||
|
||||
<string name="actionbar_webview_cope">Copy the cookies</string>
|
||||
<string name="actionbar_webview_refresh">Refresh the page</string>
|
||||
<string name="bt_open_url">open url</string>
|
||||
</resources>
|
6
app/src/main/res/values-w820dp/dimens.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
10
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#2483D9</color>
|
||||
<color name="colorPrimaryDark">#2483D9</color>
|
||||
<color name="colorAccent">#2483D9</color>
|
||||
<color name="colorRed">#ff0000</color>
|
||||
<color name="colorPink">#ffb6cf</color>
|
||||
<color name="colorWhite">#ffffffff</color>
|
||||
<color name="color_FF4081">#FF4081</color>
|
||||
</resources>
|
5
app/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
7
app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
<string name="app_name">NoMihoyoApp</string>
|
||||
|
||||
<string name="actionbar_webview_cope">复制 Cookie</string>
|
||||
<string name="actionbar_webview_refresh">刷新页面</string>
|
||||
<string name="bt_open_url">打开网页</string>
|
||||
</resources>
|
36
app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="WebViewTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<!--titleBar样式-->
|
||||
<style name="ToolbarStyle" parent="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="textStyle">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:background">?android:attr/selectableItemBackground</item>
|
||||
<item name="android:padding">15dp</item>
|
||||
<item name="android:textColor">#4D4D4D</item>
|
||||
</style>
|
||||
|
||||
<style name="lineStyle">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">1px</item>
|
||||
<item name="android:background">#D4D4D4</item>
|
||||
</style>
|
||||
</resources>
|
@ -0,0 +1,17 @@
|
||||
package com.paigramteam.nomihoyoapp;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
5
build.gradle
Normal file
@ -0,0 +1,5 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.0.2' apply false
|
||||
id 'com.android.library' version '8.0.2' apply false
|
||||
}
|
21
gradle.properties
Normal file
@ -0,0 +1,21 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Sun Jul 16 09:53:20 CST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
185
gradlew
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
16
settings.gradle
Normal file
@ -0,0 +1,16 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "NoMihoyoApp"
|
||||
include ':app'
|