🎉 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'
|