Compare commits
96 Commits
v1.0.1
...
598c0cbde4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
598c0cbde4 | ||
|
|
bce68681ac | ||
|
|
85bc4635bc | ||
|
|
c94abdb3c5 | ||
|
|
4ed9c029ca | ||
|
|
ba7352d26e | ||
|
|
b8c370893a | ||
|
|
8b592e2e48 | ||
|
|
1e8aa3a2d4 | ||
|
|
70f4df431f | ||
|
|
6c3c15a85d | ||
|
|
b84be2ecb2 | ||
|
|
faaf254f2c | ||
|
|
0776b07b12 | ||
|
|
6ad62bfdab | ||
|
|
ab2fdb28c2 | ||
|
|
5a681067b7 | ||
|
|
bdce0ecf70 | ||
|
|
6e09c6012d | ||
|
|
851a9173af | ||
|
|
7caf726381 | ||
|
|
1a055bdca7 | ||
|
|
5d5d6d2244 | ||
|
|
862af21ec2 | ||
|
|
2037328800 | ||
|
|
bb6092e3ac | ||
|
|
014fbe78bf | ||
|
|
a89b546755 | ||
|
|
490bc0f597 | ||
|
|
d0ee140a44 | ||
|
|
e4abb578f0 | ||
|
|
fdde0037f9 | ||
|
|
e2ec85c24e | ||
|
|
3fd167e889 | ||
|
|
0e78441fa1 | ||
|
|
04de960c0f | ||
|
|
a83206bf4b | ||
|
|
6a20abae19 | ||
|
|
32ddb21ed2 | ||
|
|
68d69353e6 | ||
|
|
7f859c3eda | ||
|
|
7903977b29 | ||
|
|
104cb6cd54 | ||
|
|
b5b3e2ab0d | ||
|
|
d48270467e | ||
|
|
a45716b03c | ||
|
|
c1b1b935ff | ||
|
|
1bdbe85201 | ||
|
|
dcf08dc2c0 | ||
|
|
cb484eeb95 | ||
|
|
8c0a5bbb24 | ||
|
|
b1229b54ee | ||
|
|
7c2a16a18d | ||
|
|
b9d0071302 | ||
|
|
b8b195b663 | ||
|
|
5c6f016ab6 | ||
|
|
0f5732cc77 | ||
|
|
6050c6c965 | ||
|
|
b1105dfadc | ||
|
|
25c98a1abf | ||
|
|
9612802996 | ||
|
|
744e421eb7 | ||
|
|
b390876bd5 | ||
|
|
0d5a807afd | ||
|
|
fdbfc87481 | ||
|
|
66f2274f44 | ||
|
|
3a31651c53 | ||
|
|
c9556df363 | ||
|
|
e4eabf9578 | ||
|
|
ae7368ecbc | ||
|
|
6cc810b9b1 | ||
|
|
66bedda2b4 | ||
|
|
e741e664a3 | ||
|
|
cada2debae | ||
|
|
3b6d0b8f89 | ||
|
|
42cf89f7c5 | ||
|
|
abc6130940 | ||
|
|
2932ad3a64 | ||
|
|
83cd53d23c | ||
|
|
d7aa3b7348 | ||
|
|
9b51b5c26c | ||
|
|
f389f32d32 | ||
|
|
71a270394d | ||
|
|
d4e8dec698 | ||
|
|
a97aa1f347 | ||
|
|
130459d646 | ||
|
|
4f8285fcf1 | ||
|
|
25bc0d4101 | ||
|
|
282695cc93 | ||
|
|
df9aa8e72f | ||
|
|
747b15fdea | ||
|
|
22f66d36a2 | ||
|
|
89ad8e38e5 | ||
|
|
fb802040ca | ||
|
|
72b9829dbe | ||
|
|
b177298e07 |
39
CHANGELOG.md
@@ -1,3 +1,42 @@
|
|||||||
|
## v1.0.7
|
||||||
|
* 首页任务增加分类
|
||||||
|
* 增加国际化相关配置
|
||||||
|
* 登录页状态栏适配
|
||||||
|
|
||||||
|
## v1.0.6
|
||||||
|
* 支持切换账号功能
|
||||||
|
* 登录页青龙图标随颜色主题变换
|
||||||
|
* apk大小减少2/3,只支持arm架构,不再支持x86
|
||||||
|
|
||||||
|
## v1.0.5
|
||||||
|
* 优化配置文件编辑功能
|
||||||
|
* 优化文本选择样式
|
||||||
|
* 增加页面错误toast
|
||||||
|
* 修改iOS端app icon
|
||||||
|
|
||||||
|
## 1.0.4
|
||||||
|
* 增加主题切换功能,各种颜色主题随意切换
|
||||||
|
* 修改无法停止任务的bug
|
||||||
|
* 修改脚本管理页面json解析失败的bug
|
||||||
|
* 修改变更配置文件没有及时刷新UI的bug
|
||||||
|
* 增加版本更新提醒功能(需要手机能联通github)
|
||||||
|
* 登录页面长按青龙logo可以呼出网络请求日志,再次长按可关闭
|
||||||
|
* 打开系统最近任务时展示APP的名称
|
||||||
|
* 优化使用体验
|
||||||
|
|
||||||
|
## 1.0.3
|
||||||
|
|
||||||
|
* 解决2.10.12及以下版本无法登录的问题
|
||||||
|
* 适配最新版qinglong 相关接口字段
|
||||||
|
* 其他页面排版优化
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
* 支持修改用户名密码(仅限使用用户名密码登录用户)
|
||||||
|
* 支持脚本的编辑删除
|
||||||
|
* 支持client_id登录之后只启用部分模块
|
||||||
|
* 支持两步验证
|
||||||
|
|
||||||
## 1.0.1
|
## 1.0.1
|
||||||
|
|
||||||
* 增加任务详情页
|
* 增加任务详情页
|
||||||
|
|||||||
798
LICENSE
@@ -1,201 +1,661 @@
|
|||||||
Apache License
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 2.0, January 2004
|
Version 3, 19 November 2007
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
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.
|
||||||
|
|
||||||
1. Definitions.
|
Preamble
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
The licenses for most software and other practical works are designed
|
||||||
the copyright owner that is granting the License.
|
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.
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
When we speak of free software, we are referring to freedom, not
|
||||||
other entities that control, are controlled by, or are under common
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
control with that entity. For the purposes of this definition,
|
have the freedom to distribute copies of free software (and charge for
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
them if you wish), that you receive source code or can get it if you
|
||||||
direction or management of such entity, whether by contract or
|
want it, that you can change the software or use pieces of it in new
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
free programs, and that you know you can do these things.
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
Developers that use our General Public Licenses protect your rights
|
||||||
exercising permissions granted by this License.
|
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.
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
A secondary benefit of defending all users' freedom is that
|
||||||
including but not limited to software source code, documentation
|
improvements made in alternate versions of the program, if they
|
||||||
source, and configuration files.
|
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.
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
The GNU Affero General Public License is designed specifically to
|
||||||
transformation or translation of a Source form, including but
|
ensure that, in such cases, the modified source code becomes available
|
||||||
not limited to compiled object code, generated documentation,
|
to the community. It requires the operator of a network server to
|
||||||
and conversions to other media types.
|
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.
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
An older license, called the Affero General Public License and
|
||||||
Object form, made available under the License, as indicated by a
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
copyright notice that is included in or attached to the work
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
(an example is provided in the Appendix below).
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
The precise terms and conditions for copying, distribution and
|
||||||
form, that is based on (or derived from) the Work and for which the
|
modification follow.
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
TERMS AND CONDITIONS
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
0. Definitions.
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
works, such as semiconductor masks.
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
modifications, and in Source or Object form, provided that You
|
"recipients" may be individuals or organizations.
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
Derivative Works a copy of this License; and
|
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.
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
A "covered work" means either the unmodified Program or a work based
|
||||||
stating that You changed the files; and
|
on the Program.
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
To "propagate" a work means to do anything with it that, without
|
||||||
that You distribute, all copyright, patent, trademark, and
|
permission, would make you directly or secondarily liable for
|
||||||
attribution notices from the Source form of the Work,
|
infringement under applicable copyright law, except executing it on a
|
||||||
excluding those notices that do not pertain to any part of
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
the Derivative Works; and
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
To "convey" a work means any kind of propagation that enables other
|
||||||
distribution, then any Derivative Works that You distribute must
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
include a readable copy of the attribution notices contained
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
may provide additional or different license terms and conditions
|
to the extent that it includes a convenient and prominently visible
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
tells the user that there is no warranty for the work (except to the
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
extent that warranties are provided), that licensees may convey the
|
||||||
the conditions stated in this License.
|
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.
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
1. Source Code.
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
The "source code" for a work means the preferred form of the work
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
for making modifications to it. "Object code" means any non-source
|
||||||
except as required for reasonable and customary use in describing the
|
form of a work.
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
A "Standard Interface" means an interface that either is an official
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
standard defined by a recognized standards body, or, in the case of
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
interfaces specified for a particular programming language, one that
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
is widely used among developers working in that language.
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
The "System Libraries" of an executable work include anything, other
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
unless required by applicable law (such as deliberate and grossly
|
packaging a Major Component, but which is not part of that Major
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
Component, and (b) serves only to enable use of the work with that
|
||||||
liable to You for damages, including any direct, indirect, special,
|
Major Component, or to implement a Standard Interface for which an
|
||||||
incidental, or consequential damages of any character arising as a
|
implementation is available to the public in source code form. A
|
||||||
result of this License or out of the use or inability to use the
|
"Major Component", in this context, means a major essential component
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
(kernel, window system, and so on) of the specific operating system
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
other commercial damages or losses), even if such Contributor
|
produce the work, or an object code interpreter used to run it.
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
The "Corresponding Source" for a work in object code form means all
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
the source code needed to generate, install, and (for an executable
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
work) run the object code and to modify the work, including scripts to
|
||||||
or other liability obligations and/or rights consistent with this
|
control those activities. However, it does not include the work's
|
||||||
License. However, in accepting such obligations, You may act only
|
System Libraries, or general-purpose tools or generally available free
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
programs which are used unmodified in performing those activities but
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
which are not part of the work. For example, Corresponding Source
|
||||||
defend, and hold each Contributor harmless for any liability
|
includes interface definition files associated with source files for
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
the work, and the source code for shared libraries and dynamically
|
||||||
of your accepting any such warranty or additional liability.
|
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.
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
2. Basic Permissions.
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
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.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
You may make, run and propagate covered works that you do not
|
||||||
you may not use this file except in compliance with the License.
|
convey, without conditions so long as your license otherwise remains
|
||||||
You may obtain a copy of the License at
|
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.
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
No covered work shall be deemed part of an effective technological
|
||||||
See the License for the specific language governing permissions and
|
measure under any applicable law fulfilling obligations under article
|
||||||
limitations under the License.
|
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/>.
|
||||||
|
|||||||
12
README.md
@@ -10,11 +10,10 @@ Android端去[release](https://github.com/qinglong-app/qinglong_app/releases)下
|
|||||||
|
|
||||||
iOS端暂无上架打算,用户自行下载main分支源码编译安装
|
iOS端暂无上架打算,用户自行下载main分支源码编译安装
|
||||||
|
|
||||||
目前只支持用户名密码登录,不用担心,你的用户名密码不会泄露
|
|
||||||
|
|
||||||
<p float="left">
|
<p float="left">
|
||||||
|
<img src="./art/14.jpg" width="200" />
|
||||||
<img src="./art/1.jpg" width="200" />
|
<img src="./art/1.jpg" width="200" />
|
||||||
<img src="./art/8.jpg" width="200" />
|
|
||||||
<img src="./art/9.jpg" width="200" />
|
<img src="./art/9.jpg" width="200" />
|
||||||
</p>
|
</p>
|
||||||
<p float="left">
|
<p float="left">
|
||||||
@@ -36,11 +35,12 @@ iOS端暂无上架打算,用户自行下载main分支源码编译安装
|
|||||||
|
|
||||||
<p float="left">
|
<p float="left">
|
||||||
<img src="./art/13.jpg" width="200" />
|
<img src="./art/13.jpg" width="200" />
|
||||||
|
<img src="./art/8.jpg" width="200" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### 尚未完成的功能
|
## [更新记录](./CHANGELOG.md)
|
||||||
>* 修改用户名密码
|
|
||||||
>* 两步验证
|
|
||||||
|
### 不支持的功能
|
||||||
>* 应用设置
|
>* 应用设置
|
||||||
>* 通知设置
|
>* 通知设置
|
||||||
>* 脚本管理中的增删改和调试
|
|
||||||
|
|||||||
@@ -42,19 +42,35 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "work.master.qinglongapp"
|
applicationId "work.master.qinglongapp"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
ndk {
|
||||||
|
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
|
||||||
signingConfig signingConfigs.debug
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
signingConfigs {
|
||||||
|
|
||||||
|
release {
|
||||||
|
storeFile file("qinglong.keystore")
|
||||||
|
storePassword "jiangyuesong"
|
||||||
|
keyAlias "jiangyuesong"
|
||||||
|
keyPassword "jiangyuesong"
|
||||||
|
|
||||||
|
// Optional, specify signing versions used
|
||||||
|
v1SigningEnabled true
|
||||||
|
v2SigningEnabled true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="work.master.qinglongapp">
|
package="work.master.qinglongapp">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<application
|
|
||||||
|
<application
|
||||||
android:label="青龙"
|
android:label="青龙"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package work.master.qinglong_app
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
}
|
||||||
18
android/app/src/main/res/values-night-v31/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowSplashScreenBackground">#000000</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
<item name="android:windowFullscreen">false</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
|||||||
18
android/app/src/main/res/values-v31/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowSplashScreenBackground">#ffffff</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
<item name="android:windowFullscreen">false</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.50'
|
ext.kotlin_version = '1.6.10'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|||||||
BIN
apk/app-release-v1.0.6.apk
Normal file
BIN
apk/app-release-v1.0.8.apk
Normal file
BIN
art/14.jpg
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
assets/images/chun.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/images/kuai.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/images/le.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/images/ql_white.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
assets/images/update_bg_app_top.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
assets/images/update_ic_close.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/images/xin.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
platform :ios, '9.0'
|
|
||||||
project 'Runner', {
|
project 'Runner', {
|
||||||
'Debug' => :debug,
|
'Debug' => :debug,
|
||||||
'Profile' => :release,
|
'Profile' => :release,
|
||||||
|
|||||||
@@ -1,19 +1,31 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- flutter_native_splash (0.0.1):
|
||||||
|
- Flutter
|
||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Toast
|
- Toast
|
||||||
- move_to_background (0.0.1):
|
- move_to_background (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- package_info_plus (0.4.5):
|
||||||
|
- Flutter
|
||||||
|
- path_provider_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- shared_preferences_ios (0.0.1):
|
- shared_preferences_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Toast (4.0.0)
|
- Toast (4.0.0)
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||||
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
@@ -22,20 +34,32 @@ SPEC REPOS:
|
|||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_native_splash:
|
||||||
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
fluttertoast:
|
fluttertoast:
|
||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
move_to_background:
|
move_to_background:
|
||||||
:path: ".symlinks/plugins/move_to_background/ios"
|
:path: ".symlinks/plugins/move_to_background/ios"
|
||||||
|
package_info_plus:
|
||||||
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
|
path_provider_ios:
|
||||||
|
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||||
shared_preferences_ios:
|
shared_preferences_ios:
|
||||||
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||||
fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58
|
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||||
|
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
|
||||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||||
shared_preferences_ios: aef470a42dc4675a1cdd50e3158b42e3d1232b32
|
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||||
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
|
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||||
|
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
||||||
|
|
||||||
PODFILE CHECKSUM: aea2ed8a4b05dec076f6f7cea39202b1133ed51c
|
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
|
||||||
|
|
||||||
COCOAPODS: 1.11.2
|
COCOAPODS: 1.11.2
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
2E5B253B8F6E332504CD094A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8A94003E0C6A5209401E856 /* Pods_Runner.framework */; };
|
2861C45503C8372CCC7FCAA2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0C7852E889B34711E51EF29 /* Pods_Runner.framework */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
@@ -30,10 +30,11 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
08A8D0920911B337FB46E8F3 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
6FAEBE56016E57C6396F6C5D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
423DC651AE7A04B8898F3FDB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
@@ -44,9 +45,8 @@
|
|||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
B72F12A63B85459532204337 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
D8C98252A2BD23DF365AF3A8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
BE9194DCED1166C78BAC3036 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
E0C7852E889B34711E51EF29 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D8A94003E0C6A5209401E856 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -54,27 +54,17 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
2E5B253B8F6E332504CD094A /* Pods_Runner.framework in Frameworks */,
|
2861C45503C8372CCC7FCAA2 /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
2CC5BF901E47A28826DC8B92 /* Pods */ = {
|
80959B63BA92AC28CD093868 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
6FAEBE56016E57C6396F6C5D /* Pods-Runner.debug.xcconfig */,
|
E0C7852E889B34711E51EF29 /* Pods_Runner.framework */,
|
||||||
B72F12A63B85459532204337 /* Pods-Runner.release.xcconfig */,
|
|
||||||
BE9194DCED1166C78BAC3036 /* Pods-Runner.profile.xcconfig */,
|
|
||||||
);
|
|
||||||
path = Pods;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
6AC682C528FC3A77A86CCA78 /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D8A94003E0C6A5209401E856 /* Pods_Runner.framework */,
|
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -96,8 +86,8 @@
|
|||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
2CC5BF901E47A28826DC8B92 /* Pods */,
|
BA4BC66201CBD612BFBB5DAE /* Pods */,
|
||||||
6AC682C528FC3A77A86CCA78 /* Frameworks */,
|
80959B63BA92AC28CD093868 /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -124,6 +114,16 @@
|
|||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
BA4BC66201CBD612BFBB5DAE /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D8C98252A2BD23DF365AF3A8 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
423DC651AE7A04B8898F3FDB /* Pods-Runner.release.xcconfig */,
|
||||||
|
08A8D0920911B337FB46E8F3 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@@ -131,14 +131,14 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
8CB7F14EC4C8F4D08BD21907 /* [CP] Check Pods Manifest.lock */,
|
FD0948CDBEBEC8A5B7ABF83F /* [CP] Check Pods Manifest.lock */,
|
||||||
9740EEB61CF901F6004384FC /* Run Script */,
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
8939D68CBA57A50D249AF20A /* [CP] Embed Pods Frameworks */,
|
C4AE37104D47B22A99900CA3 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -211,7 +211,21 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
};
|
};
|
||||||
8939D68CBA57A50D249AF20A /* [CP] Embed Pods Frameworks */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
C4AE37104D47B22A99900CA3 /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
@@ -228,7 +242,7 @@
|
|||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
8CB7F14EC4C8F4D08BD21907 /* [CP] Check Pods Manifest.lock */ = {
|
FD0948CDBEBEC8A5B7ABF83F /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
@@ -250,20 +264,6 @@
|
|||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "Run Script";
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
|
||||||
};
|
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@@ -321,6 +321,7 @@
|
|||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
@@ -339,9 +340,8 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
PRODUCT_NAME = "";
|
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@@ -363,7 +363,6 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = work.master.qinglongApp;
|
PRODUCT_BUNDLE_IDENTIFIER = work.master.qinglongApp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -395,6 +394,7 @@
|
|||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
@@ -419,10 +419,9 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
PRODUCT_NAME = "";
|
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
@@ -451,6 +450,7 @@
|
|||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
@@ -469,9 +469,8 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
PRODUCT_NAME = "";
|
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = work.master.qinglongApp;
|
PRODUCT_BUNDLE_IDENTIFIER = work.master.qinglongApp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -519,8 +517,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
PRODUCT_BUNDLE_IDENTIFIER = work.newtab.qinglongApp;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = work.master.qinglongApp;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1320"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
|
||||||
BuildableName = "Runner.app"
|
|
||||||
BlueprintName = "Runner"
|
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
||||||
<Testables>
|
|
||||||
</Testables>
|
|
||||||
</TestAction>
|
|
||||||
<LaunchAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
launchStyle = "0"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
|
||||||
debugServiceExtension = "internal"
|
|
||||||
allowLocationSimulation = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
|
||||||
BuildableName = "Runner.app"
|
|
||||||
BlueprintName = "Runner"
|
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
|
||||||
BuildableName = "Runner.app"
|
|
||||||
BlueprintName = "Runner"
|
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Release"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 647 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 35 KiB |
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "BrandingImage.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "BrandingImage@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "BrandingImage@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,6 +28,8 @@
|
|||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
|
<key>UIStatusBarHidden</key>
|
||||||
|
<false/>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
@@ -43,7 +45,5 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>UIStatusBarHidden</key>
|
|
||||||
<false/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:qinglong_app/base/http/http.dart';
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
import 'package:qinglong_app/module/config/config_bean.dart';
|
import 'package:qinglong_app/module/config/config_bean.dart';
|
||||||
import 'package:qinglong_app/module/env/env_bean.dart';
|
import 'package:qinglong_app/module/env/env_bean.dart';
|
||||||
|
import 'package:qinglong_app/module/home/system_bean.dart';
|
||||||
import 'package:qinglong_app/module/login/login_bean.dart';
|
import 'package:qinglong_app/module/login/login_bean.dart';
|
||||||
import 'package:qinglong_app/module/login/user_bean.dart';
|
import 'package:qinglong_app/module/login/user_bean.dart';
|
||||||
import 'package:qinglong_app/module/others/dependencies/dependency_bean.dart';
|
import 'package:qinglong_app/module/others/dependencies/dependency_bean.dart';
|
||||||
@@ -9,9 +10,17 @@ import 'package:qinglong_app/module/others/scripts/script_bean.dart';
|
|||||||
import 'package:qinglong_app/module/others/task_log/task_log_bean.dart';
|
import 'package:qinglong_app/module/others/task_log/task_log_bean.dart';
|
||||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||||
|
|
||||||
|
import '../../utils/utils.dart';
|
||||||
import 'url.dart';
|
import 'url.dart';
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
|
static Future<HttpResponse<SystemBean>> system() async {
|
||||||
|
return await Http.get<SystemBean>(
|
||||||
|
Url.system,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<LoginBean>> login(
|
static Future<HttpResponse<LoginBean>> login(
|
||||||
String userName,
|
String userName,
|
||||||
String passWord,
|
String passWord,
|
||||||
@@ -25,6 +34,34 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<HttpResponse<LoginBean>> loginOld(
|
||||||
|
String userName,
|
||||||
|
String passWord,
|
||||||
|
) async {
|
||||||
|
return await Http.post<LoginBean>(
|
||||||
|
Url.loginOld,
|
||||||
|
{
|
||||||
|
"username": userName,
|
||||||
|
"password": passWord,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<HttpResponse<LoginBean>> loginTwo(
|
||||||
|
String userName,
|
||||||
|
String passWord,
|
||||||
|
String code,
|
||||||
|
) async {
|
||||||
|
return await Http.put<LoginBean>(
|
||||||
|
Url.loginTwo,
|
||||||
|
{
|
||||||
|
"username": userName,
|
||||||
|
"password": passWord,
|
||||||
|
"code": code,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<LoginBean>> loginByClientId(
|
static Future<HttpResponse<LoginBean>> loginByClientId(
|
||||||
String id,
|
String id,
|
||||||
String secret,
|
String secret,
|
||||||
@@ -52,20 +89,33 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<NullResponse>> startTasks(List<String> crons) async {
|
static Future<HttpResponse<NullResponse>> startTasks(
|
||||||
|
List<String> crons) async {
|
||||||
return await Http.put<NullResponse>(
|
return await Http.put<NullResponse>(
|
||||||
Url.runTasks,
|
Url.runTasks,
|
||||||
crons,
|
crons,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<NullResponse>> stopTasks(List<String> crons) async {
|
static Future<HttpResponse<NullResponse>> stopTasks(
|
||||||
|
List<String> crons) async {
|
||||||
return await Http.put<NullResponse>(
|
return await Http.put<NullResponse>(
|
||||||
Url.runTasks,
|
Url.stopTasks,
|
||||||
crons,
|
crons,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<HttpResponse<NullResponse>> updatePassword(
|
||||||
|
String name, String password) async {
|
||||||
|
return await Http.put<NullResponse>(
|
||||||
|
Url.updatePassword,
|
||||||
|
{
|
||||||
|
"username": name,
|
||||||
|
"password": password,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<String>> inTimeLog(String cron) async {
|
static Future<HttpResponse<String>> inTimeLog(String cron) async {
|
||||||
return await Http.get<String>(
|
return await Http.get<String>(
|
||||||
Url.intimeLog(cron),
|
Url.intimeLog(cron),
|
||||||
@@ -73,11 +123,17 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<NullResponse>> addTask(String name, String command, String cron, {String? id}) async {
|
static Future<HttpResponse<NullResponse>> addTask(
|
||||||
|
String name, String command, String cron,
|
||||||
|
{String? id}) async {
|
||||||
var data = {"name": name, "command": command, "schedule": cron};
|
var data = {"name": name, "command": command, "schedule": cron};
|
||||||
|
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
data["_id"] = id;
|
if (Utils.isUpperVersion()) {
|
||||||
|
data["id"] = id;
|
||||||
|
} else {
|
||||||
|
data["_id"] = id;
|
||||||
|
}
|
||||||
return await Http.put<NullResponse>(
|
return await Http.put<NullResponse>(
|
||||||
Url.addTask,
|
Url.addTask,
|
||||||
data,
|
data,
|
||||||
@@ -138,7 +194,8 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<NullResponse>> saveFile(String name, String content) async {
|
static Future<HttpResponse<NullResponse>> saveFile(
|
||||||
|
String name, String content) async {
|
||||||
return await Http.post<NullResponse>(
|
return await Http.post<NullResponse>(
|
||||||
Url.saveFile,
|
Url.saveFile,
|
||||||
{"content": content, "name": name},
|
{"content": content, "name": name},
|
||||||
@@ -173,7 +230,9 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<NullResponse>> addEnv(String name, String value, String remarks, {String? id}) async {
|
static Future<HttpResponse<NullResponse>> addEnv(
|
||||||
|
String name, String value, String remarks,
|
||||||
|
{String? id}) async {
|
||||||
var data = {
|
var data = {
|
||||||
"value": value,
|
"value": value,
|
||||||
"remarks": remarks,
|
"remarks": remarks,
|
||||||
@@ -181,7 +240,12 @@ class Api {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
data["_id"] = id;
|
if (Utils.isUpperVersion()) {
|
||||||
|
data["id"] = id;
|
||||||
|
} else {
|
||||||
|
data["_id"] = id;
|
||||||
|
}
|
||||||
|
|
||||||
return await Http.put<NullResponse>(
|
return await Http.put<NullResponse>(
|
||||||
Url.addEnv,
|
Url.addEnv,
|
||||||
data,
|
data,
|
||||||
@@ -193,7 +257,8 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<NullResponse>> moveEnv(String id, int fromIndex, int toIndex) async {
|
static Future<HttpResponse<NullResponse>> moveEnv(
|
||||||
|
String id, int fromIndex, int toIndex) async {
|
||||||
return await Http.put<NullResponse>(
|
return await Http.put<NullResponse>(
|
||||||
Url.envMove(id),
|
Url.envMove(id),
|
||||||
{"fromIndex": fromIndex, "toIndex": toIndex},
|
{"fromIndex": fromIndex, "toIndex": toIndex},
|
||||||
@@ -208,7 +273,8 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<List<TaskLogBean>>> taskLog() async {
|
static Future<HttpResponse<List<TaskLogBean>>> taskLog() async {
|
||||||
return await Http.get<List<TaskLogBean>>(Url.taskLog, null, serializationName: "dirs");
|
return await Http.get<List<TaskLogBean>>(Url.taskLog, null,
|
||||||
|
serializationName: "dirs");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<String>> taskLogDetail(String name) async {
|
static Future<HttpResponse<String>> taskLogDetail(String name) async {
|
||||||
@@ -225,7 +291,31 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<String>> scriptDetail(String name, String? path) async {
|
static Future<HttpResponse<NullResponse>> updateScript(
|
||||||
|
String name, String path, String content) async {
|
||||||
|
return await Http.put<NullResponse>(
|
||||||
|
Url.scriptDetail,
|
||||||
|
{
|
||||||
|
"filename": name,
|
||||||
|
"path": path,
|
||||||
|
"content": content,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<HttpResponse<NullResponse>> delScript(
|
||||||
|
String name, String path) async {
|
||||||
|
return await Http.delete<NullResponse>(
|
||||||
|
Url.scriptDetail,
|
||||||
|
{
|
||||||
|
"filename": name,
|
||||||
|
"path": path,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<HttpResponse<String>> scriptDetail(
|
||||||
|
String name, String? path) async {
|
||||||
return await Http.get<String>(
|
return await Http.get<String>(
|
||||||
Url.scriptDetail + name,
|
Url.scriptDetail + name,
|
||||||
{
|
{
|
||||||
@@ -234,7 +324,8 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<List<DependencyBean>>> dependencies(String type) async {
|
static Future<HttpResponse<List<DependencyBean>>> dependencies(
|
||||||
|
String type) async {
|
||||||
return await Http.get<List<DependencyBean>>(
|
return await Http.get<List<DependencyBean>>(
|
||||||
Url.dependencies,
|
Url.dependencies,
|
||||||
{
|
{
|
||||||
@@ -243,7 +334,8 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<NullResponse>> dependencyReinstall(String id) async {
|
static Future<HttpResponse<NullResponse>> dependencyReinstall(
|
||||||
|
String id) async {
|
||||||
return await Http.put<NullResponse>(
|
return await Http.put<NullResponse>(
|
||||||
Url.dependencies,
|
Url.dependencies,
|
||||||
[id],
|
[id],
|
||||||
@@ -257,7 +349,8 @@ class Api {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<NullResponse>> addDependency(String name, int type) async {
|
static Future<HttpResponse<NullResponse>> addDependency(
|
||||||
|
String name, int type) async {
|
||||||
return await Http.post<NullResponse>(
|
return await Http.post<NullResponse>(
|
||||||
Url.dependencies,
|
Url.dependencies,
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:dio_log/dio_log.dart';
|
import 'package:dio_log/dio_log.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:qinglong_app/base/http/token_interceptor.dart';
|
import 'package:qinglong_app/base/http/token_interceptor.dart';
|
||||||
|
import 'package:qinglong_app/base/http/url.dart';
|
||||||
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
import 'package:qinglong_app/utils/extension.dart';
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ import '../routes.dart';
|
|||||||
|
|
||||||
class Http {
|
class Http {
|
||||||
static Dio? _dio;
|
static Dio? _dio;
|
||||||
|
static bool pushedLoginPage = false;
|
||||||
|
|
||||||
static void initDioConfig(
|
static void initDioConfig(
|
||||||
String host,
|
String host,
|
||||||
@@ -21,9 +23,9 @@ class Http {
|
|||||||
_dio = Dio(
|
_dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl: host,
|
baseUrl: host,
|
||||||
connectTimeout: 5000,
|
connectTimeout: 50000,
|
||||||
receiveTimeout: 5000,
|
receiveTimeout: 50000,
|
||||||
sendTimeout: 5000,
|
sendTimeout: 50000,
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -37,6 +39,10 @@ class Http {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void clear() {
|
||||||
|
_dio = null;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<T>> get<T>(
|
static Future<HttpResponse<T>> get<T>(
|
||||||
String uri,
|
String uri,
|
||||||
Map<String, String?>? json, {
|
Map<String, String?>? json, {
|
||||||
@@ -49,13 +55,7 @@ class Http {
|
|||||||
|
|
||||||
return decodeResponse<T>(response, serializationName, compute);
|
return decodeResponse<T>(response, serializationName, compute);
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
if (e.response?.statusCode == 401) {
|
return exceptionHandler<T>(e, uri);
|
||||||
exitLogin();
|
|
||||||
}
|
|
||||||
return HttpResponse(
|
|
||||||
success: false,
|
|
||||||
message: e.response?.statusMessage ?? e.message,
|
|
||||||
code: 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,13 +75,7 @@ class Http {
|
|||||||
compute,
|
compute,
|
||||||
);
|
);
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
if (e.response?.statusCode == 401) {
|
return exceptionHandler<T>(e, uri);
|
||||||
exitLogin();
|
|
||||||
}
|
|
||||||
return HttpResponse(
|
|
||||||
success: false,
|
|
||||||
message: e.response?.statusMessage ?? e.message,
|
|
||||||
code: 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +95,7 @@ class Http {
|
|||||||
compute,
|
compute,
|
||||||
);
|
);
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
if (e.response?.statusCode == 401) {
|
return exceptionHandler<T>(e, uri);
|
||||||
exitLogin();
|
|
||||||
}
|
|
||||||
return HttpResponse(
|
|
||||||
success: false,
|
|
||||||
message: e.response?.statusMessage ?? e.message,
|
|
||||||
code: 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,23 +114,31 @@ class Http {
|
|||||||
compute,
|
compute,
|
||||||
);
|
);
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
if (e.response?.statusCode == 401) {
|
return exceptionHandler<T>(e, uri);
|
||||||
exitLogin();
|
|
||||||
}
|
|
||||||
return HttpResponse(
|
|
||||||
success: false,
|
|
||||||
message: e.response?.statusMessage ?? e.message,
|
|
||||||
code: 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool pushedLoginPage = false;
|
|
||||||
|
|
||||||
static void exitLogin() {
|
static void exitLogin() {
|
||||||
if (!pushedLoginPage) {
|
if (!pushedLoginPage) {
|
||||||
"身份已过期,请重新登录".toast();
|
"身份已过期,请重新登录".toast();
|
||||||
pushedLoginPage = true;
|
pushedLoginPage = true;
|
||||||
navigatorState.currentState?.pushReplacementNamed(Routes.routeLogin);
|
navigatorState.currentState?.pushNamedAndRemoveUntil(Routes.routeLogin, (route) => false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static HttpResponse<T> exceptionHandler<T>(DioError e, String path) {
|
||||||
|
logger.e(e);
|
||||||
|
if (e.response?.statusCode == 401 && !Url.inWhiteList(path)) {
|
||||||
|
if (!getIt<UserInfoViewModel>().useSecretLogined) {
|
||||||
|
exitLogin();
|
||||||
|
}
|
||||||
|
return HttpResponse(success: false, message: "没有该模块的访问权限", code: 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.response != null && e.response!.data != null) {
|
||||||
|
return HttpResponse(success: false, message: e.response?.data["message"] ?? e.message, code: e.response?.data["code"] ?? 0);
|
||||||
|
} else {
|
||||||
|
return HttpResponse(success: false, message: e.message, code: e.response?.statusCode ?? 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +194,7 @@ class Http {
|
|||||||
} else {
|
} else {
|
||||||
return HttpResponse<T>(
|
return HttpResponse<T>(
|
||||||
success: false,
|
success: false,
|
||||||
code: -1000,
|
code: response.data["code"],
|
||||||
message: response.data["message"],
|
message: response.data["message"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -227,8 +223,7 @@ class HttpResponse<T> {
|
|||||||
late int code;
|
late int code;
|
||||||
T? bean;
|
T? bean;
|
||||||
|
|
||||||
HttpResponse(
|
HttpResponse({required this.success, this.message, required this.code, this.bean});
|
||||||
{required this.success, this.message, required this.code, this.bean});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeserializeAction<T> {
|
class DeserializeAction<T> {
|
||||||
|
|||||||
@@ -7,19 +7,23 @@ import '../userinfo_viewmodel.dart';
|
|||||||
class TokenInterceptor extends Interceptor {
|
class TokenInterceptor extends Interceptor {
|
||||||
@override
|
@override
|
||||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
if (getIt<UserInfoViewModel>().token != null &&
|
|
||||||
getIt<UserInfoViewModel>().token!.isNotEmpty) {
|
|
||||||
options.headers["Authorization"] =
|
|
||||||
"Bearer " + getIt<UserInfoViewModel>().token!;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.headers["User-Agent"] =
|
options.headers["User-Agent"] =
|
||||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
|
||||||
|
|
||||||
options.headers["Content-Type"] = "application/json;charset=UTF-8";
|
options.headers["Content-Type"] = "application/json;charset=UTF-8";
|
||||||
if (options.path != Url.loginByClientId) {
|
|
||||||
|
if (!Url.inWhiteList(options.path)) {
|
||||||
options.queryParameters["t"] =
|
options.queryParameters["t"] =
|
||||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
|
(DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Url.inLoginList(options.path)) {
|
||||||
|
if (getIt<UserInfoViewModel>().token != null &&
|
||||||
|
getIt<UserInfoViewModel>().token!.isNotEmpty) {
|
||||||
|
options.headers["Authorization"] =
|
||||||
|
"Bearer " + getIt<UserInfoViewModel>().token!;
|
||||||
|
}
|
||||||
|
}
|
||||||
return handler.next(options);
|
return handler.next(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,63 +2,143 @@ import '../../main.dart';
|
|||||||
import '../userinfo_viewmodel.dart';
|
import '../userinfo_viewmodel.dart';
|
||||||
|
|
||||||
class Url {
|
class Url {
|
||||||
|
static get system => "/api/system";
|
||||||
|
|
||||||
static get login => "/api/user/login";
|
static get login => "/api/user/login";
|
||||||
|
|
||||||
|
static get loginOld => "/api/login";
|
||||||
|
|
||||||
|
static get loginTwo => "/api/user/two-factor/login";
|
||||||
static const loginByClientId = "/open/auth/token";
|
static const loginByClientId = "/open/auth/token";
|
||||||
static const user = "/api/user";
|
static const user = "/api/user";
|
||||||
|
|
||||||
static get tasks => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons" : "/api/crons";
|
static const updatePassword = "/api/user";
|
||||||
|
|
||||||
static get runTasks => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/run" : "/api/crons/run";
|
static get tasks => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons"
|
||||||
|
: "/api/crons";
|
||||||
|
|
||||||
static get stopTasks => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/stop" : "/api/crons/stop";
|
static get runTasks => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons/run"
|
||||||
|
: "/api/crons/run";
|
||||||
|
|
||||||
static get taskDetail => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/" : "/api/crons/";
|
static get stopTasks => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons/stop"
|
||||||
|
: "/api/crons/stop";
|
||||||
|
|
||||||
static get addTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons" : "/api/crons";
|
static get taskDetail => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons/"
|
||||||
|
: "/api/crons/";
|
||||||
|
|
||||||
static get pinTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/pin" : "/api/crons/pin";
|
static get addTask => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons"
|
||||||
|
: "/api/crons";
|
||||||
|
|
||||||
static get unpinTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/unpin" : "/api/crons/unpin";
|
static get pinTask => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons/pin"
|
||||||
|
: "/api/crons/pin";
|
||||||
|
|
||||||
static get enableTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/enable" : "/api/crons/enable";
|
static get unpinTask => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons/unpin"
|
||||||
|
: "/api/crons/unpin";
|
||||||
|
|
||||||
static get disableTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/disable" : "/api/crons/disable";
|
static get enableTask => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons/enable"
|
||||||
|
: "/api/crons/enable";
|
||||||
|
|
||||||
static get files => getIt<UserInfoViewModel>().useSecretLogined ? "/open/configs/files" : "/api/configs/files";
|
static get disableTask => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons/disable"
|
||||||
|
: "/api/crons/disable";
|
||||||
|
|
||||||
static get configContent => getIt<UserInfoViewModel>().useSecretLogined ? "/open/configs/" : "/api/configs/";
|
static get files => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/configs/files"
|
||||||
|
: "/api/configs/files";
|
||||||
|
|
||||||
static get saveFile => getIt<UserInfoViewModel>().useSecretLogined ? "/open/configs/save" : "/api/configs/save";
|
static get configContent => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/configs/"
|
||||||
|
: "/api/configs/";
|
||||||
|
|
||||||
static get envs => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
static get saveFile => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/configs/save"
|
||||||
|
: "/api/configs/save";
|
||||||
|
|
||||||
static get addEnv => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
static get envs =>
|
||||||
|
getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
||||||
|
|
||||||
static get delEnv => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
static get addEnv =>
|
||||||
|
getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
||||||
|
|
||||||
static get disableEnvs => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs/disable" : "/api/envs/disable";
|
static get delEnv =>
|
||||||
|
getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
||||||
|
|
||||||
static get enableEnvs => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs/enable" : "/api/envs/enable";
|
static get disableEnvs => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/envs/disable"
|
||||||
|
: "/api/envs/disable";
|
||||||
|
|
||||||
static get loginLog => getIt<UserInfoViewModel>().useSecretLogined ? "/open/user/login-log" : "/api/user/login-log";
|
static get enableEnvs => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/envs/enable"
|
||||||
|
: "/api/envs/enable";
|
||||||
|
|
||||||
static get taskLog => getIt<UserInfoViewModel>().useSecretLogined ? "/open/logs" : "/api/logs";
|
static get loginLog => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/user/login-log"
|
||||||
|
: "/api/user/login-log";
|
||||||
|
|
||||||
static get taskLogDetail => getIt<UserInfoViewModel>().useSecretLogined ? "/open/logs/" : "/api/logs/";
|
static get taskLog =>
|
||||||
|
getIt<UserInfoViewModel>().useSecretLogined ? "/open/logs" : "/api/logs";
|
||||||
|
|
||||||
static get scripts => getIt<UserInfoViewModel>().useSecretLogined ? "/open/scripts/files" : "/api/scripts/files";
|
static get taskLogDetail => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/logs/"
|
||||||
|
: "/api/logs/";
|
||||||
|
|
||||||
static get scriptDetail => getIt<UserInfoViewModel>().useSecretLogined ? "/open/scripts/" : "/api/scripts/";
|
static get scripts => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/scripts/files"
|
||||||
|
: "/api/scripts/files";
|
||||||
|
|
||||||
static get dependencies => getIt<UserInfoViewModel>().useSecretLogined ? "/open/dependencies" : "/api/dependencies";
|
static get scriptUpdate => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/scripts"
|
||||||
|
: "/api/scripts";
|
||||||
|
|
||||||
static get dependencyReinstall => getIt<UserInfoViewModel>().useSecretLogined ? "/open/dependencies/reinstall" : "/api/dependencies/reinstall";
|
static get scriptDetail => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/scripts/"
|
||||||
|
: "/api/scripts/";
|
||||||
|
|
||||||
|
static get dependencies => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/dependencies"
|
||||||
|
: "/api/dependencies";
|
||||||
|
|
||||||
|
static get dependencyReinstall => getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/dependencies/reinstall"
|
||||||
|
: "/api/dependencies/reinstall";
|
||||||
|
|
||||||
static intimeLog(String cronId) {
|
static intimeLog(String cronId) {
|
||||||
return getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/$cronId/log" : "/api/crons/$cronId/log";
|
return getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/crons/$cronId/log"
|
||||||
|
: "/api/crons/$cronId/log";
|
||||||
}
|
}
|
||||||
|
|
||||||
static envMove(String envId) {
|
static envMove(String envId) {
|
||||||
return getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs/$envId/move" : "/api/envs/$envId/move";
|
return getIt<UserInfoViewModel>().useSecretLogined
|
||||||
|
? "/open/envs/$envId/move"
|
||||||
|
: "/api/envs/$envId/move";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool inWhiteList(String path) {
|
||||||
|
if (path == login ||
|
||||||
|
path == loginByClientId ||
|
||||||
|
path == loginTwo ||
|
||||||
|
path == loginOld) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool inLoginList(String path) {
|
||||||
|
if (path == login || path == loginByClientId || path == loginOld) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String checkUpdateUrl =
|
||||||
|
"https://raw.githubusercontent.com/qinglong-app/qinglong_app/main/version";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,18 @@ import 'package:qinglong_app/module/env/env_bean.dart';
|
|||||||
import 'package:qinglong_app/module/env/env_detail_page.dart';
|
import 'package:qinglong_app/module/env/env_detail_page.dart';
|
||||||
import 'package:qinglong_app/module/home/home_page.dart';
|
import 'package:qinglong_app/module/home/home_page.dart';
|
||||||
import 'package:qinglong_app/module/login/login_page.dart';
|
import 'package:qinglong_app/module/login/login_page.dart';
|
||||||
|
import 'package:qinglong_app/module/others/about_page.dart';
|
||||||
|
import 'package:qinglong_app/module/others/change_account_page.dart';
|
||||||
import 'package:qinglong_app/module/others/dependencies/add_dependency_page.dart';
|
import 'package:qinglong_app/module/others/dependencies/add_dependency_page.dart';
|
||||||
import 'package:qinglong_app/module/others/dependencies/dependency_page.dart';
|
import 'package:qinglong_app/module/others/dependencies/dependency_page.dart';
|
||||||
import 'package:qinglong_app/module/others/login_log/login_log_page.dart';
|
import 'package:qinglong_app/module/others/login_log/login_log_page.dart';
|
||||||
import 'package:qinglong_app/module/others/scripts/script_detail_page.dart';
|
import 'package:qinglong_app/module/others/scripts/script_detail_page.dart';
|
||||||
|
import 'package:qinglong_app/module/others/scripts/script_edit_page.dart';
|
||||||
import 'package:qinglong_app/module/others/scripts/script_page.dart';
|
import 'package:qinglong_app/module/others/scripts/script_page.dart';
|
||||||
import 'package:qinglong_app/module/others/task_log/task_log_detail_page.dart';
|
import 'package:qinglong_app/module/others/task_log/task_log_detail_page.dart';
|
||||||
import 'package:qinglong_app/module/others/task_log/task_log_page.dart';
|
import 'package:qinglong_app/module/others/task_log/task_log_page.dart';
|
||||||
|
import 'package:qinglong_app/module/others/theme_page.dart';
|
||||||
|
import 'package:qinglong_app/module/others/update_password_page.dart';
|
||||||
import 'package:qinglong_app/module/task/add_task_page.dart';
|
import 'package:qinglong_app/module/task/add_task_page.dart';
|
||||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||||
import 'package:qinglong_app/module/task/task_detail/task_detail_page.dart';
|
import 'package:qinglong_app/module/task/task_detail/task_detail_page.dart';
|
||||||
@@ -31,14 +36,28 @@ class Routes {
|
|||||||
static const String routeTaskLogDetail = "/log/taskDetail";
|
static const String routeTaskLogDetail = "/log/taskDetail";
|
||||||
static const String routeScript = "/script";
|
static const String routeScript = "/script";
|
||||||
static const String routeScriptDetail = "/script/detail";
|
static const String routeScriptDetail = "/script/detail";
|
||||||
|
static const String routeScriptUpdate = "/script/update";
|
||||||
|
static const String routeScriptAdd = "/script/add";
|
||||||
static const String routeDependency = "/Dependency";
|
static const String routeDependency = "/Dependency";
|
||||||
|
static const String routeUpdatePassword = "/updatePassword";
|
||||||
|
static const String routeAbout = "/about";
|
||||||
|
static const String routeTheme = "/theme";
|
||||||
|
static const String routeChangeAccount = "/changeAccount";
|
||||||
|
|
||||||
static Route<dynamic>? generateRoute(RouteSettings settings) {
|
static Route<dynamic>? generateRoute(RouteSettings settings) {
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case routeHomePage:
|
case routeHomePage:
|
||||||
return MaterialPageRoute(builder: (context) => const HomePage());
|
return MaterialPageRoute(builder: (context) => const HomePage());
|
||||||
case routeLogin:
|
case routeLogin:
|
||||||
return MaterialPageRoute(builder: (context) => const LoginPage());
|
if (settings.arguments != null) {
|
||||||
|
return MaterialPageRoute(
|
||||||
|
builder: (context) => const LoginPage(
|
||||||
|
doNotLoadLocalData: true,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return MaterialPageRoute(builder: (context) => const LoginPage());
|
||||||
|
}
|
||||||
|
|
||||||
case routeAddTask:
|
case routeAddTask:
|
||||||
if (settings.arguments != null) {
|
if (settings.arguments != null) {
|
||||||
return CupertinoPageRoute(
|
return CupertinoPageRoute(
|
||||||
@@ -49,7 +68,8 @@ class Routes {
|
|||||||
return CupertinoPageRoute(builder: (context) => const AddTaskPage());
|
return CupertinoPageRoute(builder: (context) => const AddTaskPage());
|
||||||
}
|
}
|
||||||
case routeAddDependency:
|
case routeAddDependency:
|
||||||
return CupertinoPageRoute(builder: (context) => const AddDependencyPage());
|
return CupertinoPageRoute(
|
||||||
|
builder: (context) => const AddDependencyPage());
|
||||||
case routeAddEnv:
|
case routeAddEnv:
|
||||||
if (settings.arguments != null) {
|
if (settings.arguments != null) {
|
||||||
return CupertinoPageRoute(
|
return CupertinoPageRoute(
|
||||||
@@ -100,12 +120,37 @@ class Routes {
|
|||||||
builder: (context) => TaskDetailPage(
|
builder: (context) => TaskDetailPage(
|
||||||
settings.arguments as TaskBean,
|
settings.arguments as TaskBean,
|
||||||
),
|
),
|
||||||
); case routeEnvDetail:
|
);
|
||||||
|
case routeEnvDetail:
|
||||||
return CupertinoPageRoute(
|
return CupertinoPageRoute(
|
||||||
builder: (context) => EnvDetailPage(
|
builder: (context) => EnvDetailPage(
|
||||||
settings.arguments as EnvBean,
|
settings.arguments as EnvBean,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
case routeUpdatePassword:
|
||||||
|
return CupertinoPageRoute(
|
||||||
|
builder: (context) => const UpdatePasswordPage(),
|
||||||
|
);
|
||||||
|
case routeAbout:
|
||||||
|
return CupertinoPageRoute(
|
||||||
|
builder: (context) => const AboutPage(),
|
||||||
|
);
|
||||||
|
case routeTheme:
|
||||||
|
return CupertinoPageRoute(
|
||||||
|
builder: (context) => const ThemePage(),
|
||||||
|
);
|
||||||
|
case routeChangeAccount:
|
||||||
|
return CupertinoPageRoute(
|
||||||
|
builder: (context) => const ChangeAccountPage(),
|
||||||
|
);
|
||||||
|
case routeScriptUpdate:
|
||||||
|
return CupertinoPageRoute(
|
||||||
|
builder: (context) => ScriptEditPage(
|
||||||
|
(settings.arguments as Map)["title"],
|
||||||
|
(settings.arguments as Map)["path"],
|
||||||
|
(settings.arguments as Map)["content"],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -4,3 +4,5 @@ String spUserName = "username";
|
|||||||
String spPassWord = "password";
|
String spPassWord = "password";
|
||||||
String spTheme = "dart_mode";
|
String spTheme = "dart_mode";
|
||||||
String spSecretLogined = "secret_logined";
|
String spSecretLogined = "secret_logined";
|
||||||
|
String spCustomColor = "customColor";
|
||||||
|
String spLoginHistory = "loginHistory";
|
||||||
|
|||||||
@@ -3,21 +3,27 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/sp_const.dart';
|
import 'package:qinglong_app/base/sp_const.dart';
|
||||||
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
|
import 'package:qinglong_app/main.dart';
|
||||||
import 'package:qinglong_app/utils/codeeditor_theme.dart';
|
import 'package:qinglong_app/utils/codeeditor_theme.dart';
|
||||||
import 'package:qinglong_app/utils/sp_utils.dart';
|
import 'package:qinglong_app/utils/sp_utils.dart';
|
||||||
|
|
||||||
var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel());
|
var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel());
|
||||||
const Color _primaryColor = Color(0xFF299343);
|
|
||||||
|
|
||||||
get primaryColor => _primaryColor;
|
const commonColor = Color(0xFF299343);
|
||||||
|
Color _primaryColor = commonColor;
|
||||||
|
|
||||||
class ThemeViewModel extends ChangeNotifier {
|
class ThemeViewModel extends ChangeNotifier {
|
||||||
ThemeData currentTheme = lightTheme;
|
late ThemeData currentTheme;
|
||||||
|
|
||||||
bool _isInDarkMode = false;
|
bool _isInDarkMode = false;
|
||||||
|
Color primaryColor = commonColor;
|
||||||
|
|
||||||
ThemeColors themeColor = LightThemeColors();
|
ThemeColors themeColor = LightThemeColors();
|
||||||
|
|
||||||
ThemeViewModel() {
|
ThemeViewModel() {
|
||||||
|
_primaryColor = Color(getIt<UserInfoViewModel>().primaryColor);
|
||||||
|
primaryColor = Color(getIt<UserInfoViewModel>().primaryColor);
|
||||||
var brightness = SchedulerBinding.instance!.window.platformBrightness;
|
var brightness = SchedulerBinding.instance!.window.platformBrightness;
|
||||||
_isInDarkMode = brightness == Brightness.dark;
|
_isInDarkMode = brightness == Brightness.dark;
|
||||||
changeThemeReal(_isInDarkMode, false);
|
changeThemeReal(_isInDarkMode, false);
|
||||||
@@ -31,10 +37,10 @@ class ThemeViewModel extends ChangeNotifier {
|
|||||||
_isInDarkMode = dark;
|
_isInDarkMode = dark;
|
||||||
SpUtil.putBool(spTheme, dark);
|
SpUtil.putBool(spTheme, dark);
|
||||||
if (!dark) {
|
if (!dark) {
|
||||||
currentTheme = lightTheme;
|
currentTheme = getLightTheme();
|
||||||
themeColor = LightThemeColors();
|
themeColor = LightThemeColors();
|
||||||
} else {
|
} else {
|
||||||
currentTheme = darkTheme;
|
currentTheme = getDartTheme();
|
||||||
themeColor = DartThemeColors();
|
themeColor = DartThemeColors();
|
||||||
}
|
}
|
||||||
if (notify) {
|
if (notify) {
|
||||||
@@ -44,142 +50,165 @@ class ThemeViewModel extends ChangeNotifier {
|
|||||||
|
|
||||||
get darkMode => _isInDarkMode;
|
get darkMode => _isInDarkMode;
|
||||||
|
|
||||||
|
void changePrimaryColor(Color color) {
|
||||||
|
_primaryColor = color;
|
||||||
|
primaryColor = color;
|
||||||
|
getIt<UserInfoViewModel>().updateCustomColor(color.value);
|
||||||
|
changeThemeReal(SpUtil.getBool(spTheme, defValue: false), true);
|
||||||
|
}
|
||||||
|
|
||||||
void changeTheme() {
|
void changeTheme() {
|
||||||
changeThemeReal(!SpUtil.getBool(spTheme, defValue: false), true);
|
changeThemeReal(!SpUtil.getBool(spTheme, defValue: false), true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ThemeData darkTheme = ThemeData.dark().copyWith(
|
ThemeData getLightTheme() {
|
||||||
brightness: Brightness.dark,
|
return ThemeData.light().copyWith(
|
||||||
primaryColor: const Color(0xffffffff),
|
brightness: Brightness.light,
|
||||||
scaffoldBackgroundColor: Colors.black,
|
primaryColor: _primaryColor,
|
||||||
appBarTheme: const AppBarTheme(
|
colorScheme: ColorScheme.light(
|
||||||
backgroundColor: Colors.black,
|
secondary: _primaryColor,
|
||||||
titleTextStyle: TextStyle(
|
primary: _primaryColor,
|
||||||
color: Colors.white,
|
),
|
||||||
fontSize: 18,
|
scaffoldBackgroundColor: const Color(0xfff5f5f5),
|
||||||
),
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
),
|
labelStyle: TextStyle(color: _primaryColor),
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
focusedBorder: UnderlineInputBorder(
|
||||||
labelStyle: TextStyle(color: _primaryColor),
|
borderSide: BorderSide(
|
||||||
focusedBorder: UnderlineInputBorder(
|
color: _primaryColor,
|
||||||
borderSide: BorderSide(
|
),
|
||||||
|
),
|
||||||
|
border: const UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: const UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
appBarTheme: AppBarTheme(
|
||||||
|
backgroundColor: _primaryColor,
|
||||||
|
titleTextStyle: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||||
|
selectedItemColor: _primaryColor,
|
||||||
|
),
|
||||||
|
buttonTheme: ButtonThemeData(
|
||||||
|
buttonColor: _primaryColor,
|
||||||
|
),
|
||||||
|
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||||
color: _primaryColor,
|
color: _primaryColor,
|
||||||
),
|
),
|
||||||
),
|
tabBarTheme: TabBarTheme(
|
||||||
border: UnderlineInputBorder(
|
labelStyle: const TextStyle(
|
||||||
borderSide: BorderSide(
|
fontSize: 14,
|
||||||
color: Color(0xff999999),
|
),
|
||||||
|
unselectedLabelStyle: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
labelColor: _primaryColor,
|
||||||
|
unselectedLabelColor: const Color(0xff999999),
|
||||||
|
indicator: UnderlineTabIndicator(
|
||||||
|
borderSide: BorderSide(color: _primaryColor),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
toggleableActiveColor: _primaryColor,
|
||||||
enabledBorder: UnderlineInputBorder(
|
checkboxTheme: CheckboxThemeData(
|
||||||
borderSide: BorderSide(
|
checkColor: MaterialStateProperty.resolveWith(
|
||||||
color: Color(0xff999999),
|
(Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return Colors.transparent;
|
||||||
|
}
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return Colors.white;
|
||||||
|
}
|
||||||
|
return Colors.black;
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
cupertinoOverrideTheme: NoDefaultCupertinoThemeData(
|
||||||
),
|
brightness: Brightness.light,
|
||||||
tabBarTheme: const TabBarTheme(
|
primaryColor: _primaryColor,
|
||||||
labelStyle: TextStyle(
|
scaffoldBackgroundColor: const Color(0xfff5f5f5),
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
unselectedLabelStyle: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
labelColor: Color(0xffffffff),
|
|
||||||
unselectedLabelColor: Color(0xff999999),
|
|
||||||
),
|
|
||||||
colorScheme: const ColorScheme.light(
|
|
||||||
secondary: _primaryColor,
|
|
||||||
primary: _primaryColor,
|
|
||||||
),
|
|
||||||
toggleableActiveColor: _primaryColor,
|
|
||||||
checkboxTheme: CheckboxThemeData(
|
|
||||||
checkColor: MaterialStateProperty.resolveWith(
|
|
||||||
(Set<MaterialState> states) {
|
|
||||||
if (states.contains(MaterialState.disabled)) {
|
|
||||||
return Colors.transparent;
|
|
||||||
}
|
|
||||||
if (states.contains(MaterialState.selected)) {
|
|
||||||
return Colors.white;
|
|
||||||
}
|
|
||||||
return Colors.white;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
ThemeData lightTheme = ThemeData.light().copyWith(
|
|
||||||
brightness: Brightness.light,
|
|
||||||
primaryColor: _primaryColor,
|
|
||||||
colorScheme: const ColorScheme.light(
|
|
||||||
secondary: _primaryColor,
|
|
||||||
primary: _primaryColor,
|
|
||||||
),
|
|
||||||
scaffoldBackgroundColor: const Color(0xfff5f5f5),
|
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
|
||||||
labelStyle: TextStyle(color: _primaryColor),
|
|
||||||
focusedBorder: UnderlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: _primaryColor,
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
border: UnderlineInputBorder(
|
}
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Color(0xff999999),
|
ThemeData getDartTheme() {
|
||||||
|
return ThemeData.dark().copyWith(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
primaryColor: const Color(0xffffffff),
|
||||||
|
scaffoldBackgroundColor: Colors.black,
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
titleTextStyle: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
enabledBorder: UnderlineInputBorder(
|
labelStyle: TextStyle(color: _primaryColor),
|
||||||
borderSide: BorderSide(
|
focusedBorder: UnderlineInputBorder(
|
||||||
color: Color(0xff999999),
|
borderSide: BorderSide(
|
||||||
|
color: _primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
border: const UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: const UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
tabBarTheme: const TabBarTheme(
|
||||||
),
|
labelStyle: TextStyle(
|
||||||
appBarTheme: const AppBarTheme(
|
fontSize: 14,
|
||||||
backgroundColor: _primaryColor,
|
),
|
||||||
titleTextStyle: TextStyle(
|
unselectedLabelStyle: TextStyle(
|
||||||
color: Colors.white,
|
fontSize: 14,
|
||||||
fontSize: 18,
|
),
|
||||||
),
|
labelColor: Color(0xffffffff),
|
||||||
),
|
unselectedLabelColor: Color(0xff999999),
|
||||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
),
|
||||||
selectedItemColor: _primaryColor,
|
colorScheme: ColorScheme.light(
|
||||||
),
|
secondary: _primaryColor,
|
||||||
buttonTheme: const ButtonThemeData(
|
primary: _primaryColor,
|
||||||
buttonColor: _primaryColor,
|
),
|
||||||
),
|
toggleableActiveColor: _primaryColor,
|
||||||
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
checkboxTheme: CheckboxThemeData(
|
||||||
color: _primaryColor,
|
checkColor: MaterialStateProperty.resolveWith(
|
||||||
),
|
(Set<MaterialState> states) {
|
||||||
tabBarTheme: const TabBarTheme(
|
if (states.contains(MaterialState.disabled)) {
|
||||||
labelStyle: TextStyle(
|
return Colors.transparent;
|
||||||
fontSize: 14,
|
}
|
||||||
),
|
if (states.contains(MaterialState.selected)) {
|
||||||
unselectedLabelStyle: TextStyle(
|
return Colors.white;
|
||||||
fontSize: 14,
|
}
|
||||||
),
|
return Colors.white;
|
||||||
labelColor: _primaryColor,
|
},
|
||||||
unselectedLabelColor: Color(0xff999999),
|
),
|
||||||
indicator: UnderlineTabIndicator(
|
),
|
||||||
borderSide: BorderSide(color: _primaryColor),
|
cupertinoOverrideTheme: const NoDefaultCupertinoThemeData(
|
||||||
),
|
brightness: Brightness.dark,
|
||||||
),
|
primaryColor: Color(0xffffffff),
|
||||||
toggleableActiveColor: _primaryColor,
|
scaffoldBackgroundColor: Colors.black,
|
||||||
checkboxTheme: CheckboxThemeData(
|
),
|
||||||
checkColor: MaterialStateProperty.resolveWith(
|
);
|
||||||
(Set<MaterialState> states) {
|
}
|
||||||
if (states.contains(MaterialState.disabled)) {
|
}
|
||||||
return Colors.transparent;
|
|
||||||
}
|
|
||||||
if (states.contains(MaterialState.selected)) {
|
|
||||||
return Colors.white;
|
|
||||||
}
|
|
||||||
return Colors.black;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
abstract class ThemeColors {
|
abstract class ThemeColors {
|
||||||
Color settingBgColor();
|
Color settingBgColor();
|
||||||
|
Color settingBordorColor();
|
||||||
|
|
||||||
Color titleColor();
|
Color titleColor();
|
||||||
|
|
||||||
@@ -222,6 +251,11 @@ class LightThemeColors extends ThemeColors {
|
|||||||
Color buttonBgColor() {
|
Color buttonBgColor() {
|
||||||
return _primaryColor;
|
return _primaryColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color settingBordorColor() {
|
||||||
|
return Colors.white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DartThemeColors extends ThemeColors {
|
class DartThemeColors extends ThemeColors {
|
||||||
@@ -254,4 +288,9 @@ class DartThemeColors extends ThemeColors {
|
|||||||
Color buttonBgColor() {
|
Color buttonBgColor() {
|
||||||
return const Color(0xff333333);
|
return const Color(0xff333333);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color settingBordorColor() {
|
||||||
|
return Color(0xff333333);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
170
lib/base/ui/source_code_view.dart
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart' as url_launcher;
|
||||||
|
import 'syntax_highlighter.dart';
|
||||||
|
|
||||||
|
class SourceCodeView extends StatefulWidget {
|
||||||
|
final String filePath;
|
||||||
|
final String? codeLinkPrefix;
|
||||||
|
final bool showLabelText;
|
||||||
|
final Color? iconBackgroundColor;
|
||||||
|
final Color? iconForegroundColor;
|
||||||
|
final Color? labelBackgroundColor;
|
||||||
|
final TextStyle? labelTextStyle;
|
||||||
|
final SyntaxHighlighterStyle? syntaxHighlighterStyle;
|
||||||
|
|
||||||
|
const SourceCodeView({
|
||||||
|
Key? key,
|
||||||
|
required this.filePath,
|
||||||
|
this.codeLinkPrefix,
|
||||||
|
this.showLabelText = false,
|
||||||
|
this.iconBackgroundColor,
|
||||||
|
this.iconForegroundColor,
|
||||||
|
this.labelBackgroundColor,
|
||||||
|
this.labelTextStyle,
|
||||||
|
this.syntaxHighlighterStyle,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
String? get codeLink => codeLinkPrefix == null
|
||||||
|
? null
|
||||||
|
: '$codeLinkPrefix/$filePath';
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SourceCodeViewState createState() {
|
||||||
|
return _SourceCodeViewState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SourceCodeViewState extends State<SourceCodeView> {
|
||||||
|
double _textScaleFactor = 1.0;
|
||||||
|
|
||||||
|
Widget _getCodeView(String codeContent, BuildContext context) {
|
||||||
|
codeContent = codeContent.replaceAll('\r\n', '\n');
|
||||||
|
final SyntaxHighlighterStyle style = widget.syntaxHighlighterStyle ??
|
||||||
|
(Theme.of(context).brightness == Brightness.dark
|
||||||
|
? SyntaxHighlighterStyle.darkThemeStyle()
|
||||||
|
: SyntaxHighlighterStyle.lightThemeStyle());
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints.expand(),
|
||||||
|
child: Scrollbar(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SelectableText.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: GoogleFonts.droidSansMono(fontSize: 12)
|
||||||
|
.apply(fontSizeFactor: this._textScaleFactor),
|
||||||
|
children: <TextSpan>[
|
||||||
|
DartSyntaxHighlighter(style).format(codeContent)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: DefaultTextStyle.of(context)
|
||||||
|
.style
|
||||||
|
.apply(fontSizeFactor: this._textScaleFactor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SpeedDialChild> _buildFloatingButtons({
|
||||||
|
TextStyle? labelTextStyle,
|
||||||
|
Color? iconBackgroundColor,
|
||||||
|
Color? iconForegroundColor,
|
||||||
|
Color? labelBackgroundColor,
|
||||||
|
required bool showLabelText,
|
||||||
|
}) =>
|
||||||
|
[
|
||||||
|
if (this.widget.codeLink != null)
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.content_copy),
|
||||||
|
labelWidget: showLabelText ? Text('Copy code to clipboard') : null,
|
||||||
|
backgroundColor: iconBackgroundColor,
|
||||||
|
foregroundColor: iconForegroundColor,
|
||||||
|
labelBackgroundColor: labelBackgroundColor,
|
||||||
|
labelStyle: labelTextStyle,
|
||||||
|
onTap: () async {
|
||||||
|
Clipboard.setData(ClipboardData(
|
||||||
|
text: await DefaultAssetBundle.of(context)
|
||||||
|
.loadString(widget.filePath)));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text('Code copied to clipboard!'),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (this.widget.codeLink != null)
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.open_in_new),
|
||||||
|
labelWidget: showLabelText ? Text('View code in browser') : null,
|
||||||
|
backgroundColor: iconBackgroundColor,
|
||||||
|
foregroundColor: iconForegroundColor,
|
||||||
|
labelBackgroundColor: labelBackgroundColor,
|
||||||
|
labelStyle: labelTextStyle,
|
||||||
|
onTap: () => url_launcher.launch(this.widget.codeLink!),
|
||||||
|
),
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.zoom_out),
|
||||||
|
label: showLabelText ? 'Zoom out' : null,
|
||||||
|
// labelWidget: showLabelText ? Text('Zoom out') : null,
|
||||||
|
backgroundColor: iconBackgroundColor,
|
||||||
|
foregroundColor: iconForegroundColor,
|
||||||
|
labelBackgroundColor: labelBackgroundColor,
|
||||||
|
labelStyle: labelTextStyle,
|
||||||
|
onTap: () => setState(() {
|
||||||
|
this._textScaleFactor = max(0.8, this._textScaleFactor - 0.1);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.zoom_in),
|
||||||
|
labelWidget: showLabelText ? Text('Zoom in') : null,
|
||||||
|
backgroundColor: iconBackgroundColor,
|
||||||
|
foregroundColor: iconForegroundColor,
|
||||||
|
labelBackgroundColor: labelBackgroundColor,
|
||||||
|
labelStyle: labelTextStyle,
|
||||||
|
onTap: () => setState(() {
|
||||||
|
this._textScaleFactor += 0.1;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder(
|
||||||
|
future: DefaultAssetBundle.of(context).loadString(widget.filePath),
|
||||||
|
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Padding(
|
||||||
|
padding: EdgeInsets.all(4.0),
|
||||||
|
child: _getCodeView(snapshot.data!, context),
|
||||||
|
),
|
||||||
|
floatingActionButton: SpeedDial(
|
||||||
|
renderOverlay: false,
|
||||||
|
overlayOpacity: 0,
|
||||||
|
children: _buildFloatingButtons(
|
||||||
|
labelTextStyle: widget.labelTextStyle,
|
||||||
|
iconBackgroundColor: widget.iconBackgroundColor,
|
||||||
|
iconForegroundColor: widget.iconForegroundColor,
|
||||||
|
labelBackgroundColor: widget.labelBackgroundColor,
|
||||||
|
showLabelText: widget.showLabelText,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
activeBackgroundColor: Colors.red,
|
||||||
|
activeForegroundColor: Colors.white,
|
||||||
|
icon: Icons.menu,
|
||||||
|
activeIcon: Icons.close,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
384
lib/base/ui/syntax_highlighter.dart
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
class SyntaxHighlighterStyle {
|
||||||
|
SyntaxHighlighterStyle(
|
||||||
|
{this.baseStyle,
|
||||||
|
this.numberStyle,
|
||||||
|
this.commentStyle,
|
||||||
|
this.keywordStyle,
|
||||||
|
this.stringStyle,
|
||||||
|
this.punctuationStyle,
|
||||||
|
this.classStyle,
|
||||||
|
this.constantStyle});
|
||||||
|
|
||||||
|
static SyntaxHighlighterStyle lightThemeStyle() => SyntaxHighlighterStyle(
|
||||||
|
baseStyle: const TextStyle(color: const Color(0xFF000000),height: 1,),
|
||||||
|
numberStyle: const TextStyle(color: const Color(0xFF1565C0)),
|
||||||
|
commentStyle: const TextStyle(color: const Color(0xFF9E9E9E)),
|
||||||
|
keywordStyle: const TextStyle(color: const Color(0xFF9C27B0)),
|
||||||
|
stringStyle: const TextStyle(color: const Color(0xFF43A047)),
|
||||||
|
punctuationStyle: const TextStyle(color: const Color(0xFF000000)),
|
||||||
|
classStyle: const TextStyle(color: const Color(0xFF512DA8)),
|
||||||
|
constantStyle: const TextStyle(color: const Color(0xFF795548)),
|
||||||
|
);
|
||||||
|
|
||||||
|
static SyntaxHighlighterStyle darkThemeStyle() => SyntaxHighlighterStyle(
|
||||||
|
baseStyle: const TextStyle(color: const Color(0xFFFFFFFF)),
|
||||||
|
numberStyle: const TextStyle(color: const Color(0xFF1565C0)),
|
||||||
|
commentStyle: const TextStyle(color: const Color(0xFF9E9E9E)),
|
||||||
|
keywordStyle: const TextStyle(color: const Color(0xFF80CBC4)),
|
||||||
|
stringStyle: const TextStyle(color: const Color(0xFF009688)),
|
||||||
|
punctuationStyle: const TextStyle(color: const Color(0xFFFFFFFF)),
|
||||||
|
classStyle: const TextStyle(color: const Color(0xFF009688)),
|
||||||
|
constantStyle: const TextStyle(color: const Color(0xFF795548)),
|
||||||
|
);
|
||||||
|
|
||||||
|
SyntaxHighlighterStyle copyWith({
|
||||||
|
TextStyle? baseStyle,
|
||||||
|
TextStyle? numberStyle,
|
||||||
|
TextStyle? commentStyle,
|
||||||
|
TextStyle? keywordStyle,
|
||||||
|
TextStyle? stringStyle,
|
||||||
|
TextStyle? punctuationStyle,
|
||||||
|
TextStyle? classStyle,
|
||||||
|
TextStyle? constantStyle,
|
||||||
|
}) =>
|
||||||
|
SyntaxHighlighterStyle(
|
||||||
|
baseStyle: baseStyle ?? this.baseStyle,
|
||||||
|
numberStyle: numberStyle ?? this.numberStyle,
|
||||||
|
commentStyle: commentStyle ?? this.commentStyle,
|
||||||
|
keywordStyle: keywordStyle ?? this.keywordStyle,
|
||||||
|
stringStyle: stringStyle ?? this.stringStyle,
|
||||||
|
punctuationStyle: punctuationStyle ?? this.punctuationStyle,
|
||||||
|
classStyle: classStyle ?? this.classStyle,
|
||||||
|
constantStyle: constantStyle ?? this.constantStyle,
|
||||||
|
);
|
||||||
|
|
||||||
|
final TextStyle? baseStyle;
|
||||||
|
final TextStyle? numberStyle;
|
||||||
|
final TextStyle? commentStyle;
|
||||||
|
final TextStyle? keywordStyle;
|
||||||
|
final TextStyle? stringStyle;
|
||||||
|
final TextStyle? punctuationStyle;
|
||||||
|
final TextStyle? classStyle;
|
||||||
|
final TextStyle? constantStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class SyntaxHighlighter {
|
||||||
|
// ignore: one_member_abstracts
|
||||||
|
TextSpan format(String src);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DartSyntaxHighlighter extends SyntaxHighlighter {
|
||||||
|
DartSyntaxHighlighter([this._style]) {
|
||||||
|
_spans = <_HighlightSpan>[];
|
||||||
|
_style ??= SyntaxHighlighterStyle.darkThemeStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
SyntaxHighlighterStyle? _style;
|
||||||
|
|
||||||
|
static const List<String> _keywords = const <String>[
|
||||||
|
'abstract',
|
||||||
|
'as',
|
||||||
|
'assert',
|
||||||
|
'async',
|
||||||
|
'await',
|
||||||
|
'break',
|
||||||
|
'case',
|
||||||
|
'catch',
|
||||||
|
'class',
|
||||||
|
'const',
|
||||||
|
'continue',
|
||||||
|
'default',
|
||||||
|
'deferred',
|
||||||
|
'do',
|
||||||
|
'dynamic',
|
||||||
|
'else',
|
||||||
|
'enum',
|
||||||
|
'export',
|
||||||
|
'external',
|
||||||
|
'extends',
|
||||||
|
'factory',
|
||||||
|
'false',
|
||||||
|
'final',
|
||||||
|
'finally',
|
||||||
|
'for',
|
||||||
|
'get',
|
||||||
|
'if',
|
||||||
|
'implements',
|
||||||
|
'import',
|
||||||
|
'in',
|
||||||
|
'is',
|
||||||
|
'library',
|
||||||
|
'new',
|
||||||
|
'null',
|
||||||
|
'operator',
|
||||||
|
'part',
|
||||||
|
'rethrow',
|
||||||
|
'return',
|
||||||
|
'set',
|
||||||
|
'static',
|
||||||
|
'super',
|
||||||
|
'switch',
|
||||||
|
'sync',
|
||||||
|
'this',
|
||||||
|
'throw',
|
||||||
|
'true',
|
||||||
|
'try',
|
||||||
|
'typedef',
|
||||||
|
'var',
|
||||||
|
'void',
|
||||||
|
'while',
|
||||||
|
'with',
|
||||||
|
'yield'
|
||||||
|
];
|
||||||
|
|
||||||
|
static const List<String> _builtInTypes = const <String>[
|
||||||
|
'int',
|
||||||
|
'double',
|
||||||
|
'num',
|
||||||
|
'bool'
|
||||||
|
];
|
||||||
|
|
||||||
|
late String _src;
|
||||||
|
late StringScanner _scanner;
|
||||||
|
|
||||||
|
late List<_HighlightSpan> _spans;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextSpan format(String src) {
|
||||||
|
_src = src;
|
||||||
|
_scanner = StringScanner(_src);
|
||||||
|
|
||||||
|
if (_generateSpans()) {
|
||||||
|
// Successfully parsed the code
|
||||||
|
final List<TextSpan> formattedText = <TextSpan>[];
|
||||||
|
int currentPosition = 0;
|
||||||
|
|
||||||
|
for (_HighlightSpan span in _spans) {
|
||||||
|
if (currentPosition != span.start)
|
||||||
|
formattedText
|
||||||
|
.add(TextSpan(text: _src.substring(currentPosition, span.start)));
|
||||||
|
|
||||||
|
formattedText.add(TextSpan(
|
||||||
|
style: span.textStyle(_style), text: span.textForSpan(_src)));
|
||||||
|
|
||||||
|
currentPosition = span.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPosition != _src.length)
|
||||||
|
formattedText
|
||||||
|
.add(TextSpan(text: _src.substring(currentPosition, _src.length)));
|
||||||
|
|
||||||
|
return TextSpan(style: _style!.baseStyle, children: formattedText);
|
||||||
|
} else {
|
||||||
|
// Parsing failed, return with only basic formatting
|
||||||
|
return TextSpan(style: _style!.baseStyle, text: src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _generateSpans() {
|
||||||
|
int lastLoopPosition = _scanner.position;
|
||||||
|
|
||||||
|
while (!_scanner.isDone) {
|
||||||
|
// Skip White space
|
||||||
|
_scanner.scan(RegExp(r'\s+'));
|
||||||
|
|
||||||
|
// Block comments
|
||||||
|
if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.comment,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line comments
|
||||||
|
if (_scanner.scan('##')) {
|
||||||
|
final int startComment = _scanner.lastMatch!.start;
|
||||||
|
|
||||||
|
bool eof = false;
|
||||||
|
int endComment;
|
||||||
|
if (_scanner.scan(RegExp(r'.*\n'))) {
|
||||||
|
endComment = _scanner.lastMatch!.end - 1;
|
||||||
|
} else {
|
||||||
|
eof = true;
|
||||||
|
endComment = _src.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_spans.add(
|
||||||
|
_HighlightSpan(_HighlightType.comment, startComment, endComment));
|
||||||
|
|
||||||
|
if (eof) break;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw r"String"
|
||||||
|
if (_scanner.scan(RegExp(r'r".*"'))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw r'String'
|
||||||
|
if (_scanner.scan(RegExp(r"r'.*'"))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiline """String"""
|
||||||
|
if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiline '''String'''
|
||||||
|
if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "String"
|
||||||
|
if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'String'
|
||||||
|
if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.string,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double
|
||||||
|
if (_scanner.scan(RegExp(r'\d+\.\d+'))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.number,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integer
|
||||||
|
if (_scanner.scan(RegExp(r'\d+'))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.number,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Punctuation
|
||||||
|
if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.punctuation,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta data
|
||||||
|
if (_scanner.scan(RegExp(r'@\w+'))) {
|
||||||
|
_spans.add(_HighlightSpan(_HighlightType.keyword,
|
||||||
|
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Words
|
||||||
|
if (_scanner.scan(RegExp(r'\w+'))) {
|
||||||
|
_HighlightType? type;
|
||||||
|
|
||||||
|
String word = _scanner.lastMatch![0]!;
|
||||||
|
if (word.startsWith('_')) word = word.substring(1);
|
||||||
|
|
||||||
|
if (_keywords.contains(word))
|
||||||
|
type = _HighlightType.keyword;
|
||||||
|
else if (_builtInTypes.contains(word))
|
||||||
|
type = _HighlightType.keyword;
|
||||||
|
else if (_firstLetterIsUpperCase(word))
|
||||||
|
type = _HighlightType.klass;
|
||||||
|
else if (word.length >= 2 &&
|
||||||
|
word.startsWith('k') &&
|
||||||
|
_firstLetterIsUpperCase(word.substring(1)))
|
||||||
|
type = _HighlightType.constant;
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
_spans.add(_HighlightSpan(
|
||||||
|
type, _scanner.lastMatch!.start, _scanner.lastMatch!.end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this loop did anything
|
||||||
|
if (lastLoopPosition == _scanner.position) {
|
||||||
|
// Failed to parse this file, abort gracefully
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lastLoopPosition = _scanner.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
_simplify();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _simplify() {
|
||||||
|
for (int i = _spans.length - 2; i >= 0; i -= 1) {
|
||||||
|
if (_spans[i].type == _spans[i + 1].type &&
|
||||||
|
_spans[i].end == _spans[i + 1].start) {
|
||||||
|
_spans[i] =
|
||||||
|
_HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end);
|
||||||
|
_spans.removeAt(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _firstLetterIsUpperCase(String str) {
|
||||||
|
if (str.isNotEmpty) {
|
||||||
|
final String first = str.substring(0, 1);
|
||||||
|
return first == first.toUpperCase();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _HighlightType {
|
||||||
|
number,
|
||||||
|
comment,
|
||||||
|
keyword,
|
||||||
|
string,
|
||||||
|
punctuation,
|
||||||
|
klass,
|
||||||
|
constant
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HighlightSpan {
|
||||||
|
_HighlightSpan(this.type, this.start, this.end);
|
||||||
|
final _HighlightType type;
|
||||||
|
final int start;
|
||||||
|
final int end;
|
||||||
|
|
||||||
|
String textForSpan(String src) {
|
||||||
|
return src.substring(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle? textStyle(SyntaxHighlighterStyle? style) {
|
||||||
|
if (type == _HighlightType.number)
|
||||||
|
return style!.numberStyle;
|
||||||
|
else if (type == _HighlightType.comment)
|
||||||
|
return style!.commentStyle;
|
||||||
|
else if (type == _HighlightType.keyword)
|
||||||
|
return style!.keywordStyle;
|
||||||
|
else if (type == _HighlightType.string)
|
||||||
|
return style!.stringStyle;
|
||||||
|
else if (type == _HighlightType.punctuation)
|
||||||
|
return style!.punctuationStyle;
|
||||||
|
else if (type == _HighlightType.klass)
|
||||||
|
return style!.classStyle;
|
||||||
|
else if (type == _HighlightType.constant)
|
||||||
|
return style!.constantStyle;
|
||||||
|
else
|
||||||
|
return style!.baseStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,38 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
import 'package:qinglong_app/utils/sp_utils.dart';
|
import 'package:qinglong_app/utils/sp_utils.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
import 'sp_const.dart';
|
import 'sp_const.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
class UserInfoViewModel {
|
class UserInfoViewModel {
|
||||||
|
int _primaryColor = commonColor.value;
|
||||||
String? _token;
|
String? _token;
|
||||||
String? _host = "";
|
String? _host = "";
|
||||||
String? _userName;
|
String? _userName;
|
||||||
String? _passWord;
|
String? _passWord;
|
||||||
bool _useSecertLogined = false;
|
bool _useSecertLogined = false;
|
||||||
|
|
||||||
|
List<UserInfoBean> historyAccounts = [];
|
||||||
|
|
||||||
UserInfoViewModel() {
|
UserInfoViewModel() {
|
||||||
String userInfoJson = SpUtil.getString(spUserInfo);
|
_token = SpUtil.getString(spUserInfo);
|
||||||
_userName = SpUtil.getString(spUserName);
|
_userName = SpUtil.getString(spUserName);
|
||||||
_passWord = SpUtil.getString(spPassWord);
|
_passWord = SpUtil.getString(spPassWord);
|
||||||
|
_primaryColor = SpUtil.getInt(spCustomColor, defValue: commonColor.value);
|
||||||
|
|
||||||
_useSecertLogined = SpUtil.getBool(spSecretLogined, defValue: false);
|
_useSecertLogined = SpUtil.getBool(spSecretLogined, defValue: false);
|
||||||
_host = SpUtil.getString(spHost, defValue: '');
|
_host = SpUtil.getString(spHost, defValue: '');
|
||||||
|
|
||||||
if (userInfoJson.isNotEmpty) {
|
List<dynamic>? tempList =
|
||||||
_token = userInfoJson;
|
jsonDecode(SpUtil.getString(spLoginHistory, defValue: '[]'));
|
||||||
|
|
||||||
|
if (tempList != null && tempList.isNotEmpty) {
|
||||||
|
for (Map<String, dynamic> value in tempList) {
|
||||||
|
historyAccounts.add(UserInfoBean.fromJson(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,18 +41,28 @@ class UserInfoViewModel {
|
|||||||
SpUtil.putString(spUserInfo, token);
|
SpUtil.putString(spUserInfo, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateUserName(String userName, String password) {
|
void updateUserName(
|
||||||
|
String host, String userName, String password, bool secretLogin) {
|
||||||
|
updateHost(host);
|
||||||
|
_useSecretLogin(secretLogin);
|
||||||
_userName = userName;
|
_userName = userName;
|
||||||
_passWord = password;
|
_passWord = password;
|
||||||
SpUtil.putString(spUserName, userName);
|
SpUtil.putString(spUserName, userName);
|
||||||
SpUtil.putString(spPassWord, password);
|
SpUtil.putString(spPassWord, password);
|
||||||
|
|
||||||
|
save2HistoryAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void useSecretLogin(bool use) {
|
void _useSecretLogin(bool use) {
|
||||||
_useSecertLogined = use;
|
_useSecertLogined = use;
|
||||||
SpUtil.putBool(spSecretLogined, _useSecertLogined);
|
SpUtil.putBool(spSecretLogined, _useSecertLogined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateCustomColor(int color) {
|
||||||
|
_primaryColor = color;
|
||||||
|
SpUtil.putInt(spCustomColor, color);
|
||||||
|
}
|
||||||
|
|
||||||
void updateHost(String host) {
|
void updateHost(String host) {
|
||||||
_host = host;
|
_host = host;
|
||||||
SpUtil.putString(spHost, host);
|
SpUtil.putString(spHost, host);
|
||||||
@@ -54,7 +78,65 @@ class UserInfoViewModel {
|
|||||||
|
|
||||||
bool get useSecretLogined => _useSecertLogined;
|
bool get useSecretLogined => _useSecertLogined;
|
||||||
|
|
||||||
|
int get primaryColor => _primaryColor;
|
||||||
|
|
||||||
bool isLogined() {
|
bool isLogined() {
|
||||||
return token != null && token!.isNotEmpty;
|
return token != null && token!.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void save2HistoryAccount() {
|
||||||
|
if (_host == null || _host!.isEmpty) return;
|
||||||
|
if (_userName == null || _userName!.isEmpty) return;
|
||||||
|
if (_passWord == null || _passWord!.isEmpty) return;
|
||||||
|
|
||||||
|
//如果已经存在host,那就更新
|
||||||
|
|
||||||
|
"login success $_host $userName $passWord".log();
|
||||||
|
|
||||||
|
historyAccounts.removeWhere((element) => element.host == _host);
|
||||||
|
|
||||||
|
historyAccounts.insert(
|
||||||
|
0,
|
||||||
|
UserInfoBean(
|
||||||
|
userName: _userName,
|
||||||
|
password: _passWord,
|
||||||
|
useSecretLogined: _useSecertLogined,
|
||||||
|
host: _host));
|
||||||
|
|
||||||
|
SpUtil.putString(spLoginHistory, jsonEncode(historyAccounts));
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeHistoryAccount(String? host) {
|
||||||
|
if (host == null || host.isEmpty) return;
|
||||||
|
|
||||||
|
historyAccounts.removeWhere((element) => element.host == host);
|
||||||
|
|
||||||
|
SpUtil.putString(spLoginHistory, jsonEncode(historyAccounts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserInfoBean {
|
||||||
|
String? userName;
|
||||||
|
String? password;
|
||||||
|
bool useSecretLogined = false;
|
||||||
|
String? host;
|
||||||
|
|
||||||
|
UserInfoBean(
|
||||||
|
{this.userName, this.password, this.useSecretLogined = false, this.host});
|
||||||
|
|
||||||
|
UserInfoBean.fromJson(Map<String, dynamic> json) {
|
||||||
|
userName = json['userName'];
|
||||||
|
password = json['password'];
|
||||||
|
useSecretLogined = json['useSecretLogined'] ?? false;
|
||||||
|
host = json['host'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['userName'] = this.userName;
|
||||||
|
data['password'] = this.password;
|
||||||
|
data['useSecretLogined'] = this.useSecretLogined;
|
||||||
|
data['host'] = this.host;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'package:qinglong_app/module/config/config_bean.dart';
|
import 'package:qinglong_app/module/config/config_bean.dart';
|
||||||
import 'package:qinglong_app/module/env/env_bean.dart';
|
import 'package:qinglong_app/module/env/env_bean.dart';
|
||||||
|
import 'package:qinglong_app/module/home/system_bean.dart';
|
||||||
import 'package:qinglong_app/module/login/login_bean.dart';
|
import 'package:qinglong_app/module/login/login_bean.dart';
|
||||||
import 'package:qinglong_app/module/login/user_bean.dart';
|
import 'package:qinglong_app/module/login/user_bean.dart';
|
||||||
import 'package:qinglong_app/module/others/dependencies/dependency_bean.dart';
|
import 'package:qinglong_app/module/others/dependencies/dependency_bean.dart';
|
||||||
@@ -33,6 +34,10 @@ class JsonConversion$Json {
|
|||||||
return EnvBean.jsonConversion(json) as M;
|
return EnvBean.jsonConversion(json) as M;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(type == (SystemBean).toString()){
|
||||||
|
return SystemBean.jsonConversion(json) as M;
|
||||||
|
}
|
||||||
|
|
||||||
if(type == (LoginBean).toString()){
|
if(type == (LoginBean).toString()){
|
||||||
return LoginBean.jsonConversion(json) as M;
|
return LoginBean.jsonConversion(json) as M;
|
||||||
}
|
}
|
||||||
@@ -60,7 +65,7 @@ class JsonConversion$Json {
|
|||||||
if(type == (TaskBean).toString()){
|
if(type == (TaskBean).toString()){
|
||||||
return TaskBean.jsonConversion(json) as M;
|
return TaskBean.jsonConversion(json) as M;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Exception("not found");
|
throw Exception("not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +78,10 @@ class JsonConversion$Json {
|
|||||||
return data.map<EnvBean>((e) => EnvBean.jsonConversion(e)).toList() as M;
|
return data.map<EnvBean>((e) => EnvBean.jsonConversion(e)).toList() as M;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(<SystemBean>[] is M){
|
||||||
|
return data.map<SystemBean>((e) => SystemBean.jsonConversion(e)).toList() as M;
|
||||||
|
}
|
||||||
|
|
||||||
if(<LoginBean>[] is M){
|
if(<LoginBean>[] is M){
|
||||||
return data.map<LoginBean>((e) => LoginBean.jsonConversion(e)).toList() as M;
|
return data.map<LoginBean>((e) => LoginBean.jsonConversion(e)).toList() as M;
|
||||||
}
|
}
|
||||||
@@ -100,7 +109,7 @@ class JsonConversion$Json {
|
|||||||
if(<TaskBean>[] is M){
|
if(<TaskBean>[] is M){
|
||||||
return data.map<TaskBean>((e) => TaskBean.jsonConversion(e)).toList() as M;
|
return data.map<TaskBean>((e) => TaskBean.jsonConversion(e)).toList() as M;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Exception("not found");
|
throw Exception("not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:logger/logger.dart';
|
|||||||
import 'package:qinglong_app/base/theme.dart';
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
import 'package:qinglong_app/module/login/login_page.dart';
|
import 'package:qinglong_app/module/login/login_page.dart';
|
||||||
import 'package:qinglong_app/utils/sp_utils.dart';
|
import 'package:qinglong_app/utils/sp_utils.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
|
||||||
import 'base/routes.dart';
|
import 'base/routes.dart';
|
||||||
import 'base/userinfo_viewmodel.dart';
|
import 'base/userinfo_viewmodel.dart';
|
||||||
@@ -54,22 +55,37 @@ class QlAppState extends ConsumerState<QlApp> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
},
|
},
|
||||||
child: MaterialApp(
|
child: MediaQuery(
|
||||||
locale: const Locale('zh', 'cn'),
|
data: MediaQueryData.fromWindow(WidgetsBinding.instance!.window).copyWith(
|
||||||
navigatorKey: navigatorState,
|
textScaleFactor: 1,
|
||||||
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
|
),
|
||||||
onGenerateRoute: (setting) {
|
child: MaterialApp(
|
||||||
return Routes.generateRoute(setting);
|
title: "青龙",
|
||||||
},
|
locale: const Locale('zh', 'CN'),
|
||||||
home: Builder(
|
navigatorKey: navigatorState,
|
||||||
builder: (context) {
|
localizationsDelegates: const [
|
||||||
if (!kReleaseMode) {
|
GlobalMaterialLocalizations.delegate,
|
||||||
showDebugBtn(context, btnColor: Colors.blue);
|
GlobalWidgetsLocalizations.delegate,
|
||||||
}
|
GlobalCupertinoLocalizations.delegate,
|
||||||
return getIt<UserInfoViewModel>().isLogined() ? const HomePage() : const LoginPage();
|
],
|
||||||
},
|
supportedLocales: const [
|
||||||
|
Locale('zh', 'CN'),
|
||||||
|
Locale('en', 'US'),
|
||||||
|
],
|
||||||
|
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
|
||||||
|
onGenerateRoute: (setting) {
|
||||||
|
return Routes.generateRoute(setting);
|
||||||
|
},
|
||||||
|
home: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
showDebugBtn(context);
|
||||||
|
}
|
||||||
|
return getIt<UserInfoViewModel>().isLogined() ? const HomePage() : const LoginPage();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// home: LoginPage(),
|
||||||
),
|
),
|
||||||
// home: LoginPage(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/http/api.dart';
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
import 'package:qinglong_app/base/http/http.dart';
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
import 'package:qinglong_app/base/theme.dart';
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
|
import 'package:qinglong_app/module/config/config_page.dart';
|
||||||
import 'package:qinglong_app/module/config/config_viewmodel.dart';
|
import 'package:qinglong_app/module/config/config_viewmodel.dart';
|
||||||
import 'package:qinglong_app/utils/extension.dart';
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
import '../../base/ui/syntax_highlighter.dart';
|
||||||
|
|
||||||
class ConfigEditPage extends ConsumerStatefulWidget {
|
class ConfigEditPage extends ConsumerStatefulWidget {
|
||||||
final String content;
|
final String content;
|
||||||
@@ -18,18 +25,33 @@ class ConfigEditPage extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
||||||
late TextEditingController _controller;
|
String text = "";
|
||||||
FocusNode node = FocusNode();
|
String pre_text = "";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_controller = TextEditingController(text: widget.content);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance?.addPostFrameCallback(
|
text = widget.content;
|
||||||
(timeStamp) {
|
pre_text = widget.content;
|
||||||
node.requestFocus();
|
}
|
||||||
},
|
|
||||||
);
|
void replaceText(String oldText, String newText) {
|
||||||
|
pre_text = text;
|
||||||
|
|
||||||
|
if (oldText.isEmpty) {
|
||||||
|
text = text + newText;
|
||||||
|
} else {
|
||||||
|
if (text.contains(oldText)) {
|
||||||
|
text = text.replaceAll(oldText, newText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearText() {
|
||||||
|
pre_text = text;
|
||||||
|
text = "";
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -45,11 +67,11 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
|||||||
InkWell(
|
InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
HttpResponse<NullResponse> response =
|
HttpResponse<NullResponse> response =
|
||||||
await Api.saveFile(widget.title, _controller.text);
|
await Api.saveFile(widget.title, text);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
"提交成功".toast();
|
"提交成功".toast();
|
||||||
ref.read(configProvider).loadContent(widget.title);
|
ref.read(configProvider).loadContent(widget.title);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop(widget.title);
|
||||||
} else {
|
} else {
|
||||||
(response.message ?? "").toast();
|
(response.message ?? "").toast();
|
||||||
}
|
}
|
||||||
@@ -71,23 +93,233 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Container(
|
body: Column(
|
||||||
padding: const EdgeInsets.only(
|
children: [
|
||||||
left: 15,
|
Padding(
|
||||||
right: 15,
|
padding: const EdgeInsets.symmetric(
|
||||||
),
|
vertical: 10,
|
||||||
child: SingleChildScrollView(
|
),
|
||||||
child: TextField(
|
child: SizedBox(
|
||||||
focusNode: node,
|
height: 30,
|
||||||
style: TextStyle(
|
child: ListView(
|
||||||
color: ref.read(themeProvider).themeColor.descColor(),
|
padding: EdgeInsets.symmetric(
|
||||||
fontSize: 14,
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
width: 1),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"插入",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
edit("插入内容", "", "请输入插入的内容,换行符自己添加");
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
edit("替换内容", "原内容", "新内容");
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
width: 1),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"替换",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
edit("删除内容", "输入要删除的内容", "");
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
width: 1),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"删除",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
clearText();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
width: 1),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"清空",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
text = pre_text;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
width: 1),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"撤销本次操作",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
controller: _controller,
|
|
||||||
minLines: 1,
|
|
||||||
maxLines: 100,
|
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CodeWidget(content: text,),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void edit(String title, String oldDesc, String newDesc) {
|
||||||
|
String oldText = "";
|
||||||
|
String newText = "";
|
||||||
|
showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => CupertinoAlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
oldDesc.isNotEmpty
|
||||||
|
? Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
oldText = value;
|
||||||
|
},
|
||||||
|
maxLines: 3,
|
||||||
|
minLines: 1,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: oldDesc,
|
||||||
|
),
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
newDesc.isNotEmpty
|
||||||
|
? Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
newText = value;
|
||||||
|
},
|
||||||
|
maxLines: 3,
|
||||||
|
minLines: 1,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: newDesc,
|
||||||
|
),
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
replaceText(oldText, newText);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_highlight/flutter_highlight.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/base_state_widget.dart';
|
import 'package:qinglong_app/base/base_state_widget.dart';
|
||||||
import 'package:qinglong_app/base/routes.dart';
|
import 'package:qinglong_app/base/routes.dart';
|
||||||
import 'package:qinglong_app/base/theme.dart';
|
|
||||||
import 'package:qinglong_app/base/ui/abs_underline_tabindicator.dart';
|
import 'package:qinglong_app/base/ui/abs_underline_tabindicator.dart';
|
||||||
|
import 'package:qinglong_app/base/ui/empty_widget.dart';
|
||||||
import 'package:qinglong_app/main.dart';
|
import 'package:qinglong_app/main.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
import '../../base/ui/syntax_highlighter.dart';
|
||||||
import 'config_viewmodel.dart';
|
import 'config_viewmodel.dart';
|
||||||
|
|
||||||
class ConfigPage extends StatefulWidget {
|
class ConfigPage extends StatefulWidget {
|
||||||
@@ -17,8 +20,9 @@ class ConfigPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ConfigPageState extends State<ConfigPage>
|
class ConfigPageState extends State<ConfigPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
||||||
TabController? _tabController;
|
int _initIndex = 0;
|
||||||
|
BuildContext? childContext;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -29,51 +33,46 @@ class ConfigPageState extends State<ConfigPage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseStateWidget<ConfigViewModel>(
|
return BaseStateWidget<ConfigViewModel>(
|
||||||
builder: (ref, model, child) {
|
builder: (ref, model, child) {
|
||||||
_tabController ??=
|
if (model.list.isEmpty) {
|
||||||
TabController(length: model.list.length, vsync: this);
|
return const EmptyWidget();
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return DefaultTabController(
|
||||||
children: [
|
length: model.list.length,
|
||||||
TabBar(
|
initialIndex: _initIndex,
|
||||||
controller: _tabController,
|
child: Builder(builder: (context) {
|
||||||
tabs: model.list
|
childContext = context;
|
||||||
.map((e) => Tab(
|
return Column(
|
||||||
text: e.title,
|
children: [
|
||||||
))
|
TabBar(
|
||||||
.toList(),
|
tabs: model.list
|
||||||
isScrollable: true,
|
.map((e) => Tab(
|
||||||
indicator: AbsUnderlineTabIndicator(
|
text: e.title,
|
||||||
wantWidth: 20,
|
))
|
||||||
borderSide: BorderSide(
|
.toList(),
|
||||||
color: Theme.of(context).primaryColor,
|
isScrollable: true,
|
||||||
width: 2,
|
indicator: AbsUnderlineTabIndicator(
|
||||||
|
wantWidth: 20,
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
),
|
child: TabBarView(
|
||||||
Expanded(
|
children: model.list
|
||||||
child: TabBarView(
|
.map(
|
||||||
controller: _tabController,
|
(e) => CodeWidget(
|
||||||
children: model.list
|
content: model.content[e.title] ?? "",
|
||||||
.map(
|
|
||||||
(e) => SingleChildScrollView(
|
|
||||||
child: HighlightView(
|
|
||||||
model.content[e.title] ?? "",
|
|
||||||
language: "sh",
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 15,
|
|
||||||
),
|
),
|
||||||
theme: ref
|
)
|
||||||
.watch(themeProvider)
|
.toList(),
|
||||||
.themeColor
|
),
|
||||||
.codeEditorTheme(),
|
),
|
||||||
tabSize: 14,
|
],
|
||||||
),
|
);
|
||||||
),
|
}),
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
model: configProvider,
|
model: configProvider,
|
||||||
@@ -84,11 +83,63 @@ class ConfigPageState extends State<ConfigPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void editMe(WidgetRef ref) {
|
void editMe(WidgetRef ref) {
|
||||||
if (_tabController == null || _tabController!.length == 0) return;
|
if (childContext == null) return;
|
||||||
navigatorState.currentState?.pushNamed(Routes.routeConfigEdit, arguments: {
|
navigatorState.currentState?.pushNamed(Routes.routeConfigEdit, arguments: {
|
||||||
"title": ref.read(configProvider).list[_tabController?.index ?? 0].title,
|
"title": ref
|
||||||
"content": ref.read(configProvider).content[
|
.read(configProvider)
|
||||||
ref.read(configProvider).list[_tabController?.index ?? 0].title]
|
.list[DefaultTabController.of(childContext!)?.index ?? 0]
|
||||||
|
.title,
|
||||||
|
"content": ref.read(configProvider).content[ref
|
||||||
|
.read(configProvider)
|
||||||
|
.list[DefaultTabController.of(childContext!)?.index ?? 0]
|
||||||
|
.title]
|
||||||
|
}).then((value) async {
|
||||||
|
if (value != null && (value as String).isNotEmpty) {
|
||||||
|
await ref.read(configProvider).loadContent(value);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CodeWidget extends StatefulWidget {
|
||||||
|
final String content;
|
||||||
|
|
||||||
|
const CodeWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.content,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CodeWidget> createState() => _CodeWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CodeWidgetState extends State<CodeWidget>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SelectableText.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: GoogleFonts.droidSansMono(fontSize: 14).apply(
|
||||||
|
fontSizeFactor: 1,
|
||||||
|
),
|
||||||
|
children: <TextSpan>[
|
||||||
|
DartSyntaxHighlighter(SyntaxHighlighterStyle.lightThemeStyle())
|
||||||
|
.format(widget.content)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: DefaultTextStyle.of(context).style.apply(
|
||||||
|
fontSizeFactor: 1,
|
||||||
|
),
|
||||||
|
selectionWidthStyle: BoxWidthStyle.max,
|
||||||
|
selectionHeightStyle: BoxHeightStyle.max,
|
||||||
|
autofocus: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
}
|
}
|
||||||
|
|||||||
13
lib/module/env/env_bean.dart
vendored
@@ -10,19 +10,12 @@ class EnvBean {
|
|||||||
String? name;
|
String? name;
|
||||||
String? remarks;
|
String? remarks;
|
||||||
|
|
||||||
EnvBean(
|
EnvBean({this.value, this.sId, this.created, this.status, this.timestamp, this.name, this.remarks});
|
||||||
{this.value,
|
|
||||||
this.sId,
|
|
||||||
this.created,
|
|
||||||
this.status,
|
|
||||||
this.timestamp,
|
|
||||||
this.name,
|
|
||||||
this.remarks});
|
|
||||||
|
|
||||||
EnvBean.fromJson(Map<String, dynamic> json) {
|
EnvBean.fromJson(Map<String, dynamic> json) {
|
||||||
value = json['value'];
|
value = json['value'];
|
||||||
sId = json['_id'];
|
sId = json.containsKey('_id') ? json['_id'].toString() : (json.containsKey('id') ? json['id'].toString() : "");
|
||||||
created = json['created'];
|
created = int.tryParse(json['created'].toString());
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
timestamp = json['timestamp'];
|
timestamp = json['timestamp'];
|
||||||
name = json['name'];
|
name = json['name'];
|
||||||
|
|||||||
18
lib/module/env/env_detail_page.dart
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -235,13 +237,23 @@ class _TaskDetailPageState extends ConsumerState<EnvDetailPage> {
|
|||||||
content: Text("确认删除环境变量 ${widget.envBean.name ?? ""} 吗"),
|
content: Text("确认删除环境变量 ${widget.envBean.name ?? ""} 吗"),
|
||||||
actions: [
|
actions: [
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("取消"),
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("确定"),
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await ref.read(envProvider).delEnv(widget.envBean.sId!);
|
await ref.read(envProvider).delEnv(widget.envBean.sId!);
|
||||||
@@ -302,6 +314,8 @@ class EnvDetailCell extends ConsumerWidget {
|
|||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
desc!,
|
desc!,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
|
selectionHeightStyle: BoxHeightStyle.max,
|
||||||
|
selectionWidthStyle: BoxWidthStyle.max,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (taped != null) {
|
if (taped != null) {
|
||||||
taped!();
|
taped!();
|
||||||
|
|||||||
65
lib/module/env/env_page.dart
vendored
@@ -152,7 +152,7 @@ class EnvItemCell extends StatelessWidget {
|
|||||||
Navigator.of(context).pushNamed(Routes.routeAddEnv, arguments: bean);
|
Navigator.of(context).pushNamed(Routes.routeAddEnv, arguments: bean);
|
||||||
},
|
},
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
icon: CupertinoIcons.pencil,
|
icon: CupertinoIcons.pencil_outline,
|
||||||
),
|
),
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
@@ -208,17 +208,6 @@ class EnvItemCell extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
|
||||||
width: 5,
|
|
||||||
),
|
|
||||||
bean.status == 1
|
|
||||||
? const Icon(
|
|
||||||
Icons.dnd_forwardslash,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.red,
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
const Spacer(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -249,31 +238,41 @@ class EnvItemCell extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
border: Border.all(color: primaryColor, width: 1),
|
border: Border.all(color: ref.watch(themeProvider).primaryColor, width: 1),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
"$index",
|
"$index",
|
||||||
style: TextStyle(color: primaryColor, fontSize: 12),
|
style: TextStyle(color: ref.watch(themeProvider).primaryColor, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 5,
|
width: 5,
|
||||||
),
|
),
|
||||||
Expanded(
|
bean.status == 1
|
||||||
child: Material(
|
? const Icon(
|
||||||
color: Colors.transparent,
|
Icons.dnd_forwardslash,
|
||||||
child: Text(
|
size: 12,
|
||||||
bean.remarks ?? "-",
|
color: Colors.red,
|
||||||
maxLines: 1,
|
)
|
||||||
style: TextStyle(
|
: const SizedBox.shrink(),
|
||||||
height: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
const SizedBox(
|
||||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
width: 5,
|
||||||
fontSize: 12,
|
),
|
||||||
),
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
bean.remarks ?? "-",
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
height: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||||
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -319,13 +318,23 @@ class EnvItemCell extends StatelessWidget {
|
|||||||
content: Text("确认删除环境变量 ${bean.name ?? ""} 吗"),
|
content: Text("确认删除环境变量 ${bean.name ?? ""} 吗"),
|
||||||
actions: [
|
actions: [
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("取消"),
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("确定"),
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
ref.read(envProvider).delEnv(bean.sId!);
|
ref.read(envProvider).delEnv(bean.sId!);
|
||||||
|
|||||||
4
lib/module/env/env_viewmodel.dart
vendored
@@ -3,6 +3,7 @@ import 'package:qinglong_app/base/base_viewmodel.dart';
|
|||||||
import 'package:qinglong_app/base/http/api.dart';
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
import 'package:qinglong_app/base/http/http.dart';
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
import 'package:qinglong_app/module/env/env_bean.dart';
|
import 'package:qinglong_app/module/env/env_bean.dart';
|
||||||
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
|
||||||
var envProvider = ChangeNotifierProvider((ref) => EnvViewModel());
|
var envProvider = ChangeNotifierProvider((ref) => EnvViewModel());
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ class EnvViewModel extends BaseViewModel {
|
|||||||
Future<void> delEnv(String id) async {
|
Future<void> delEnv(String id) async {
|
||||||
HttpResponse<NullResponse> result = await Api.delEnv(id);
|
HttpResponse<NullResponse> result = await Api.delEnv(id);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
"删除成功".toast();
|
||||||
list.removeWhere((element) => element.sId == id);
|
list.removeWhere((element) => element.sId == id);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
@@ -53,6 +55,7 @@ class EnvViewModel extends BaseViewModel {
|
|||||||
HttpResponse<NullResponse> response = await Api.enableEnv(sId);
|
HttpResponse<NullResponse> response = await Api.enableEnv(sId);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
"启用成功".toast();
|
||||||
list.firstWhere((element) => element.sId == sId).status = 0;
|
list.firstWhere((element) => element.sId == sId).status = 0;
|
||||||
success();
|
success();
|
||||||
} else {
|
} else {
|
||||||
@@ -62,6 +65,7 @@ class EnvViewModel extends BaseViewModel {
|
|||||||
HttpResponse<NullResponse> response = await Api.disableEnv(sId);
|
HttpResponse<NullResponse> response = await Api.disableEnv(sId);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
"禁用成功".toast();
|
||||||
list.firstWhere((element) => element.sId == sId).status = 1;
|
list.firstWhere((element) => element.sId == sId).status = 1;
|
||||||
success();
|
success();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
import 'package:qinglong_app/base/routes.dart';
|
import 'package:qinglong_app/base/routes.dart';
|
||||||
import 'package:qinglong_app/module/config/config_page.dart';
|
import 'package:qinglong_app/module/config/config_page.dart';
|
||||||
import 'package:qinglong_app/module/env/env_page.dart';
|
import 'package:qinglong_app/module/env/env_page.dart';
|
||||||
|
import 'package:qinglong_app/module/home/system_bean.dart';
|
||||||
import 'package:qinglong_app/module/others/other_page.dart';
|
import 'package:qinglong_app/module/others/other_page.dart';
|
||||||
import 'package:qinglong_app/module/task/task_page.dart';
|
import 'package:qinglong_app/module/task/task_page.dart';
|
||||||
import 'package:move_to_background/move_to_background.dart';
|
import 'package:move_to_background/move_to_background.dart';
|
||||||
|
import 'package:qinglong_app/utils/update_utils.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import '../../utils/utils.dart';
|
||||||
|
|
||||||
class HomePage extends ConsumerStatefulWidget {
|
class HomePage extends ConsumerStatefulWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
const HomePage({Key? key}) : super(key: key);
|
||||||
@@ -17,19 +24,38 @@ class HomePage extends ConsumerStatefulWidget {
|
|||||||
_HomePageState createState() => _HomePageState();
|
_HomePageState createState() => _HomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends ConsumerState<HomePage> {
|
class _HomePageState extends ConsumerState<HomePage>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
int _index = 0;
|
int _index = 0;
|
||||||
String _title = "";
|
String _title = "";
|
||||||
|
bool isNewYear = false;
|
||||||
|
|
||||||
List<IndexBean> titles = [];
|
List<IndexBean> titles = [];
|
||||||
|
|
||||||
GlobalKey<ConfigPageState> configKey = GlobalKey();
|
GlobalKey<ConfigPageState> configKey = GlobalKey();
|
||||||
|
|
||||||
|
late final AnimationController _controller = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 1000),
|
||||||
|
vsync: this,
|
||||||
|
)..repeat();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
isNewYearDuration();
|
||||||
|
|
||||||
initTitles();
|
initTitles();
|
||||||
_title = titles[0].title;
|
_title = titles[0].title;
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||||
|
update();
|
||||||
|
getSystemBean();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -136,8 +162,30 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|||||||
items: titles
|
items: titles
|
||||||
.map(
|
.map(
|
||||||
(e) => BottomNavigationBarItem(
|
(e) => BottomNavigationBarItem(
|
||||||
icon: Icon(e.icon),
|
icon: (e.celebrate.isNotEmpty)
|
||||||
activeIcon: Icon(e.checkedIcon),
|
? Image.asset(
|
||||||
|
e.celebrate,
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
|
: Icon(e.icon),
|
||||||
|
activeIcon: (e.celebrate.isNotEmpty)
|
||||||
|
? AnimatedBuilder(
|
||||||
|
builder: (context, _) {
|
||||||
|
return Transform.rotate(
|
||||||
|
angle: _controller.value * 2.0 * math.pi,
|
||||||
|
child: Image.asset(
|
||||||
|
e.celebrate,
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
animation: _controller,
|
||||||
|
)
|
||||||
|
: Icon(e.checkedIcon),
|
||||||
label: e.title,
|
label: e.title,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -148,7 +196,12 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|||||||
_title = titles[index].title;
|
_title = titles[index].title;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
|
elevation: 0,
|
||||||
|
selectedFontSize: 12,
|
||||||
|
unselectedFontSize: 12,
|
||||||
type: BottomNavigationBarType.fixed,
|
type: BottomNavigationBarType.fixed,
|
||||||
|
showSelectedLabels: !isNewYear,
|
||||||
|
showUnselectedLabels: !isNewYear,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -161,6 +214,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|||||||
CupertinoIcons.timer,
|
CupertinoIcons.timer,
|
||||||
CupertinoIcons.timer_fill,
|
CupertinoIcons.timer_fill,
|
||||||
"定时任务",
|
"定时任务",
|
||||||
|
celebrate: !isNewYear ? "" : "assets/images/xin.png",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
titles.add(
|
titles.add(
|
||||||
@@ -168,6 +222,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|||||||
CupertinoIcons.hammer,
|
CupertinoIcons.hammer,
|
||||||
CupertinoIcons.hammer_fill,
|
CupertinoIcons.hammer_fill,
|
||||||
"环境变量",
|
"环境变量",
|
||||||
|
celebrate: !isNewYear ? "" : "assets/images/chun.png",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
titles.add(
|
titles.add(
|
||||||
@@ -175,22 +230,55 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|||||||
CupertinoIcons.settings,
|
CupertinoIcons.settings,
|
||||||
CupertinoIcons.settings_solid,
|
CupertinoIcons.settings_solid,
|
||||||
"配置文件",
|
"配置文件",
|
||||||
|
celebrate: !isNewYear ? "" : "assets/images/kuai.png",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
titles.add(
|
titles.add(
|
||||||
IndexBean(
|
IndexBean(
|
||||||
CupertinoIcons.cube,
|
CupertinoIcons.cube,
|
||||||
CupertinoIcons.cube_fill,
|
CupertinoIcons.cube_fill,
|
||||||
"其他",
|
"其他功能",
|
||||||
|
celebrate: !isNewYear ? "" : "assets/images/le.png",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update() async {
|
||||||
|
String? result = await UpdateUtils().checkUpdate();
|
||||||
|
if (result != null && result.isNotEmpty) {
|
||||||
|
UpdateDialog updateDialog = UpdateDialog(context,
|
||||||
|
title: "发现新版本", updateContent: "版本号:v$result", onUpdate: () {
|
||||||
|
UpdateUtils.launchURL(result);
|
||||||
|
});
|
||||||
|
updateDialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void isNewYearDuration() {
|
||||||
|
DateTime date = DateTime.now();
|
||||||
|
DateTime dateYear1 = DateTime(2022, 1, 29);
|
||||||
|
DateTime dateYear2 = DateTime(2022, 2, 6);
|
||||||
|
|
||||||
|
if (date.isAfter(dateYear1) && date.isBefore(dateYear2)) {
|
||||||
|
isNewYear = true;
|
||||||
|
} else {
|
||||||
|
isNewYear = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getSystemBean() async {
|
||||||
|
var bean = await Api.system();
|
||||||
|
if (bean.success) {
|
||||||
|
Utils.systemBean.version = bean.bean?.version ?? "2.10.13";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IndexBean {
|
class IndexBean {
|
||||||
IconData icon;
|
IconData icon;
|
||||||
IconData checkedIcon;
|
IconData checkedIcon;
|
||||||
String title;
|
String title;
|
||||||
|
String celebrate;
|
||||||
|
|
||||||
IndexBean(this.icon, this.checkedIcon, this.title);
|
IndexBean(this.icon, this.checkedIcon, this.title, {this.celebrate = ""});
|
||||||
}
|
}
|
||||||
|
|||||||
23
lib/module/home/system_bean.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
||||||
|
|
||||||
|
@JsonConversion()
|
||||||
|
class SystemBean {
|
||||||
|
String? version;
|
||||||
|
|
||||||
|
SystemBean({this.version});
|
||||||
|
|
||||||
|
SystemBean.fromJson(Map<String, dynamic> json) {
|
||||||
|
version = json['version'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['version'] = this.version;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SystemBean jsonConversion(Map<String, dynamic> json) {
|
||||||
|
return SystemBean.fromJson(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ class LoginBean {
|
|||||||
token = json['token'];
|
token = json['token'];
|
||||||
lastip = json['lastip'];
|
lastip = json['lastip'];
|
||||||
lastaddr = json['lastaddr'];
|
lastaddr = json['lastaddr'];
|
||||||
lastlogon = json['lastlogon'];
|
lastlogon = int.tryParse(json['lastlogon'].toString());
|
||||||
retries = json['retries'];
|
retries = json['retries'];
|
||||||
platform = json['platform'];
|
platform = json['platform'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import 'package:dio_log/dio_log.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/http/api.dart';
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
import 'package:qinglong_app/base/http/http.dart';
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
@@ -9,21 +11,25 @@ import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
|||||||
import 'package:qinglong_app/main.dart';
|
import 'package:qinglong_app/main.dart';
|
||||||
import 'package:qinglong_app/module/login/user_bean.dart';
|
import 'package:qinglong_app/module/login/user_bean.dart';
|
||||||
import 'package:qinglong_app/utils/extension.dart';
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
import 'package:qinglong_app/utils/login_helper.dart';
|
||||||
|
import 'package:qinglong_app/utils/update_utils.dart';
|
||||||
import 'package:qinglong_app/utils/utils.dart';
|
import 'package:qinglong_app/utils/utils.dart';
|
||||||
import 'package:flip_card/flip_card.dart';
|
import 'package:flip_card/flip_card.dart';
|
||||||
|
|
||||||
import 'login_bean.dart';
|
|
||||||
|
|
||||||
class LoginPage extends ConsumerStatefulWidget {
|
class LoginPage extends ConsumerStatefulWidget {
|
||||||
const LoginPage({Key? key}) : super(key: key);
|
final bool doNotLoadLocalData;
|
||||||
|
|
||||||
|
const LoginPage({
|
||||||
|
Key? key,
|
||||||
|
this.doNotLoadLocalData = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_LoginPageState createState() => _LoginPageState();
|
_LoginPageState createState() => _LoginPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginPageState extends ConsumerState<LoginPage> {
|
class _LoginPageState extends ConsumerState<LoginPage> {
|
||||||
final TextEditingController _hostController =
|
final TextEditingController _hostController = TextEditingController();
|
||||||
TextEditingController(text: getIt<UserInfoViewModel>().host);
|
|
||||||
final TextEditingController _userNameController = TextEditingController();
|
final TextEditingController _userNameController = TextEditingController();
|
||||||
final TextEditingController _passwordController = TextEditingController();
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
final TextEditingController _cIdController = TextEditingController();
|
final TextEditingController _cIdController = TextEditingController();
|
||||||
@@ -37,29 +43,30 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
useSecretLogin = getIt<UserInfoViewModel>().useSecretLogined;
|
if (!widget.doNotLoadLocalData) {
|
||||||
|
_hostController.text = getIt<UserInfoViewModel>().host ?? "";
|
||||||
if (getIt<UserInfoViewModel>().userName != null &&
|
useSecretLogin = getIt<UserInfoViewModel>().useSecretLogined;
|
||||||
getIt<UserInfoViewModel>().userName!.isNotEmpty) {
|
if (getIt<UserInfoViewModel>().userName != null && getIt<UserInfoViewModel>().userName!.isNotEmpty) {
|
||||||
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
||||||
_cIdController.text = getIt<UserInfoViewModel>().userName!;
|
_cIdController.text = getIt<UserInfoViewModel>().userName!;
|
||||||
|
} else {
|
||||||
|
_userNameController.text = getIt<UserInfoViewModel>().userName!;
|
||||||
|
}
|
||||||
|
rememberPassword = true;
|
||||||
} else {
|
} else {
|
||||||
_userNameController.text = getIt<UserInfoViewModel>().userName!;
|
rememberPassword = false;
|
||||||
}
|
}
|
||||||
rememberPassword = true;
|
if (getIt<UserInfoViewModel>().passWord != null && getIt<UserInfoViewModel>().passWord!.isNotEmpty) {
|
||||||
} else {
|
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
||||||
rememberPassword = false;
|
_cSecretController.text = getIt<UserInfoViewModel>().passWord!;
|
||||||
}
|
} else {
|
||||||
if (getIt<UserInfoViewModel>().passWord != null &&
|
_passwordController.text = getIt<UserInfoViewModel>().passWord!;
|
||||||
getIt<UserInfoViewModel>().passWord!.isNotEmpty) {
|
}
|
||||||
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
|
||||||
_cSecretController.text = getIt<UserInfoViewModel>().passWord!;
|
|
||||||
} else {
|
|
||||||
_passwordController.text = getIt<UserInfoViewModel>().passWord!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getIt<UserInfoViewModel>().updateToken("");
|
getIt<UserInfoViewModel>().updateToken("");
|
||||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||||
|
update();
|
||||||
if (useSecretLogin) {
|
if (useSecretLogin) {
|
||||||
cardKey.currentState?.toggleCard();
|
cardKey.currentState?.toggleCard();
|
||||||
}
|
}
|
||||||
@@ -68,295 +75,340 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ColoredBox(
|
return Scaffold(
|
||||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
body: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
child: SizedBox(
|
value: ref.watch(themeProvider).darkMode == true ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
|
||||||
height: MediaQuery.of(context).size.height,
|
child: ColoredBox(
|
||||||
child: Stack(
|
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||||
children: [
|
child: SizedBox(
|
||||||
Positioned(
|
height: MediaQuery.of(context).size.height,
|
||||||
bottom: 0,
|
child: Stack(
|
||||||
child: Image.asset(
|
children: [
|
||||||
"assets/images/login_bg.png",
|
Positioned(
|
||||||
width: MediaQuery.of(context).size.width,
|
bottom: 0,
|
||||||
),
|
child: Image.asset(
|
||||||
),
|
"assets/images/login_bg.png",
|
||||||
Scaffold(
|
width: MediaQuery.of(context).size.width,
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 40,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height / 10,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 50,
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
child: Image.asset(
|
|
||||||
"assets/images/login_tip.png",
|
|
||||||
height: 45,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Positioned(
|
|
||||||
top: 10,
|
|
||||||
left: 0,
|
|
||||||
child: Text(
|
|
||||||
"登录",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 30,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 5,
|
|
||||||
right: 0,
|
|
||||||
child: Image.asset(
|
|
||||||
"assets/images/ql.png",
|
|
||||||
height: 45,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height / 15,
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
"域名:",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
controller: _hostController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
|
||||||
hintText: "http://1.1.1.1:5700",
|
|
||||||
),
|
|
||||||
autofocus: false,
|
|
||||||
),
|
|
||||||
FlipCard(
|
|
||||||
key: cardKey,
|
|
||||||
onFlipDone: (back) {
|
|
||||||
useSecretLogin = back;
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
direction: FlipDirection.HORIZONTAL,
|
|
||||||
front: SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 15,
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
"用户名:",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
controller: _userNameController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
contentPadding:
|
|
||||||
EdgeInsets.fromLTRB(0, 5, 0, 5),
|
|
||||||
hintText: "请输入用户名",
|
|
||||||
),
|
|
||||||
autofocus: false,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 15,
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
"密码:",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
controller: _passwordController,
|
|
||||||
obscureText: true,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
contentPadding:
|
|
||||||
EdgeInsets.fromLTRB(0, 5, 0, 5),
|
|
||||||
hintText: "请输入密码",
|
|
||||||
),
|
|
||||||
autofocus: false,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
back: SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 15,
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
"client_id:",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
controller: _cIdController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
contentPadding:
|
|
||||||
EdgeInsets.fromLTRB(0, 5, 0, 5),
|
|
||||||
hintText: "请输入client_id",
|
|
||||||
),
|
|
||||||
autofocus: false,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 15,
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
"client_secret:",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
controller: _cSecretController,
|
|
||||||
obscureText: true,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
contentPadding:
|
|
||||||
EdgeInsets.fromLTRB(0, 5, 0, 5),
|
|
||||||
hintText: "请输入client_secret",
|
|
||||||
),
|
|
||||||
autofocus: false,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Checkbox(
|
|
||||||
value: rememberPassword,
|
|
||||||
onChanged: (checked) {
|
|
||||||
rememberPassword = checked ?? false;
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
"记住密码/client",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
cardKey.currentState?.toggleCard();
|
|
||||||
WidgetsBinding.instance
|
|
||||||
?.addPostFrameCallback((timeStamp) {
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
loginByUserName() ? "client_id登录" : "用户名密码登录",
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 30,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width - 80,
|
|
||||||
child: IgnorePointer(
|
|
||||||
ignoring: !canClickLoginBtn(),
|
|
||||||
child: CupertinoButton(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 5,
|
|
||||||
),
|
|
||||||
color: canClickLoginBtn()
|
|
||||||
? ref
|
|
||||||
.watch(themeProvider)
|
|
||||||
.themeColor
|
|
||||||
.buttonBgColor()
|
|
||||||
: Theme.of(context)
|
|
||||||
.primaryColor
|
|
||||||
.withOpacity(0.4),
|
|
||||||
child: isLoading
|
|
||||||
? const CupertinoActivityIndicator()
|
|
||||||
: const Text(
|
|
||||||
"登 录",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Http.pushedLoginPage = false;
|
|
||||||
Utils.hideKeyBoard(context);
|
|
||||||
getIt<UserInfoViewModel>()
|
|
||||||
.updateHost(_hostController.text);
|
|
||||||
if (loginByUserName()) {
|
|
||||||
login(_userNameController.text,
|
|
||||||
_passwordController.text);
|
|
||||||
} else {
|
|
||||||
login(_cIdController.text,
|
|
||||||
_cSecretController.text);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 40,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height / 10,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Image.asset(
|
||||||
|
"assets/images/login_tip.png",
|
||||||
|
height: 45,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onDoubleTap: () {
|
||||||
|
if (debugBtnIsShow()) {
|
||||||
|
dismissDebugBtn();
|
||||||
|
} else {
|
||||||
|
showDebugBtn(context, btnColor: ref.watch(themeProvider).primaryColor);
|
||||||
|
}
|
||||||
|
WidgetsBinding.instance?.endOfFrame;
|
||||||
|
},
|
||||||
|
child: ColorFiltered(
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
ref.watch(themeProvider).primaryColor,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
child: Image.asset(
|
||||||
|
"assets/images/ql.png",
|
||||||
|
height: 45,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height / 15,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"域名:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
controller: _hostController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "http://1.1.1.1:5700",
|
||||||
|
),
|
||||||
|
autofocus: false,
|
||||||
|
),
|
||||||
|
FlipCard(
|
||||||
|
key: cardKey,
|
||||||
|
flipOnTouch: false,
|
||||||
|
onFlipDone: (back) {
|
||||||
|
useSecretLogin = back;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
direction: FlipDirection.HORIZONTAL,
|
||||||
|
front: SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"用户名:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
controller: _userNameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "请输入用户名",
|
||||||
|
),
|
||||||
|
autofocus: false,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"密码:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
controller: _passwordController,
|
||||||
|
obscureText: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "请输入密码",
|
||||||
|
),
|
||||||
|
autofocus: false,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
back: SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"client_id:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
controller: _cIdController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "请输入client_id",
|
||||||
|
),
|
||||||
|
autofocus: false,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"client_secret:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
controller: _cSecretController,
|
||||||
|
obscureText: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "请输入client_secret",
|
||||||
|
),
|
||||||
|
autofocus: false,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 25,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: rememberPassword,
|
||||||
|
onChanged: (checked) {
|
||||||
|
rememberPassword = checked ?? false;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"记住密码/client",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
cardKey.currentState?.toggleCard();
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
loginByUserName() ? "client_id登录" : "账号登录",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 40,
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width - 80,
|
||||||
|
child: IgnorePointer(
|
||||||
|
ignoring: !canClickLoginBtn(),
|
||||||
|
child: CupertinoButton(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
color: canClickLoginBtn() ? ref.watch(themeProvider).primaryColor : ref.watch(themeProvider).primaryColor.withOpacity(0.4),
|
||||||
|
child: isLoading
|
||||||
|
? const CupertinoActivityIndicator()
|
||||||
|
: const Text(
|
||||||
|
"登 录",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Http.pushedLoginPage = false;
|
||||||
|
Utils.hideKeyBoard(context);
|
||||||
|
if (loginByUserName()) {
|
||||||
|
login(_userNameController.text, _passwordController.text);
|
||||||
|
} else {
|
||||||
|
login(_cIdController.text, _cSecretController.text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 10,
|
||||||
|
child: (getIt<UserInfoViewModel>().historyAccounts.isEmpty)
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: Center(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: PopupMenuButton<UserInfoBean>(
|
||||||
|
onSelected: (UserInfoBean result) {
|
||||||
|
selected(result);
|
||||||
|
},
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuEntry<UserInfoBean>>[
|
||||||
|
...getIt<UserInfoViewModel>()
|
||||||
|
.historyAccounts
|
||||||
|
.map((e) => PopupMenuItem<UserInfoBean>(
|
||||||
|
value: e,
|
||||||
|
child: buildCell(e),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
],
|
||||||
|
child: Text(
|
||||||
|
"切换账号",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -368,60 +420,154 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
|||||||
return !useSecretLogin;
|
return !useSecretLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoginHelper? helper;
|
||||||
|
|
||||||
Future<void> login(String userName, String password) async {
|
Future<void> login(String userName, String password) async {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
HttpResponse<LoginBean> response;
|
|
||||||
|
|
||||||
if (loginByUserName()) {
|
helper = LoginHelper(useSecretLogin, _hostController.text, userName, password, rememberPassword);
|
||||||
response = await Api.login(userName, password);
|
var response = await helper!.login();
|
||||||
} else {
|
dealLoginResponse(response);
|
||||||
response = await Api.loginByClientId(userName, password);
|
}
|
||||||
}
|
|
||||||
if (response.success) {
|
|
||||||
getIt<UserInfoViewModel>().updateToken(response.bean?.token ?? "");
|
|
||||||
getIt<UserInfoViewModel>().useSecretLogin(!loginByUserName());
|
|
||||||
if (rememberPassword) {
|
|
||||||
getIt<UserInfoViewModel>().updateUserName(userName, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!loginByUserName()) {
|
void dealLoginResponse(int response) {
|
||||||
Navigator.of(context).pushReplacementNamed(Routes.routeHomePage);
|
isLoading = false;
|
||||||
} else {
|
if (response == LoginHelper.success) {
|
||||||
HttpResponse<UserBean> userResponse = await Api.user();
|
Navigator.of(context).pushReplacementNamed(Routes.routeHomePage);
|
||||||
if (userResponse.success) {
|
} else if (response == LoginHelper.failed) {
|
||||||
if (userResponse.bean != null &&
|
loginFailed();
|
||||||
userResponse.bean!.twoFactorActivated != null &&
|
|
||||||
userResponse.bean!.twoFactorActivated!) {
|
|
||||||
("你已开启两步验证,App暂不支持").toast();
|
|
||||||
isLoading = false;
|
|
||||||
setState(() {});
|
|
||||||
} else {
|
|
||||||
Navigator.of(context).pushReplacementNamed(Routes.routeHomePage);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(response.message ?? "请检查网络情况").toast();
|
|
||||||
isLoading = false;
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
(response.message ?? "请检查网络情况").toast();
|
twoFact();
|
||||||
isLoading = false;
|
|
||||||
setState(() {});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void loginFailed() {
|
||||||
|
isLoading = false;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
bool canClickLoginBtn() {
|
bool canClickLoginBtn() {
|
||||||
if (isLoading) return false;
|
if (isLoading) return false;
|
||||||
|
|
||||||
if (_hostController.text.isEmpty) return false;
|
if (_hostController.text.isEmpty) return false;
|
||||||
if (!loginByUserName()) {
|
if (!loginByUserName()) {
|
||||||
return _cIdController.text.isNotEmpty &&
|
return _cIdController.text.isNotEmpty && _cSecretController.text.isNotEmpty;
|
||||||
_cSecretController.text.isNotEmpty;
|
|
||||||
} else {
|
} else {
|
||||||
return _userNameController.text.isNotEmpty &&
|
return _userNameController.text.isNotEmpty && _passwordController.text.isNotEmpty;
|
||||||
_passwordController.text.isNotEmpty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void twoFact() {
|
||||||
|
String twoFact = "";
|
||||||
|
showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => CupertinoAlertDialog(
|
||||||
|
title: const Text("两步验证"),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
twoFact = value;
|
||||||
|
},
|
||||||
|
maxLines: 1,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "请输入code",
|
||||||
|
),
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
if (helper != null) {
|
||||||
|
var response = await helper!.loginTwice(twoFact);
|
||||||
|
dealLoginResponse(response);
|
||||||
|
} else {
|
||||||
|
"状态异常,请重新点登录按钮".toast();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)).then((value) {
|
||||||
|
if (value == null) {
|
||||||
|
isLoading = false;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() async {
|
||||||
|
String? result = await UpdateUtils().checkUpdate();
|
||||||
|
if (result != null && result.isNotEmpty) {
|
||||||
|
UpdateDialog updateDialog = UpdateDialog(context, title: "发现新版本", updateContent: "版本号:v${result}", onUpdate: () {
|
||||||
|
UpdateUtils.launchURL(result);
|
||||||
|
});
|
||||||
|
updateDialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildCell(UserInfoBean bean) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
bean.host ?? "",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
subtitle: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 10.0),
|
||||||
|
child: Text(
|
||||||
|
bean.userName ?? "",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selected(UserInfoBean result) {
|
||||||
|
_hostController.text = result.host ?? "";
|
||||||
|
if (result.useSecretLogined) {
|
||||||
|
_cIdController.text = result.userName ?? "";
|
||||||
|
_cSecretController.text = result.password ?? "";
|
||||||
|
if (cardKey.currentState?.isFront ?? false) {
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||||
|
cardKey.currentState?.toggleCard();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_userNameController.text = result.userName ?? "";
|
||||||
|
_passwordController.text = result.password ?? "";
|
||||||
|
if (!(cardKey.currentState?.isFront ?? false)) {
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||||
|
cardKey.currentState?.toggleCard();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rememberPassword = true;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
158
lib/module/others/about_page.dart
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
|
import 'package:qinglong_app/utils/update_utils.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import '../../main.dart';
|
||||||
|
|
||||||
|
/// @author NewTab
|
||||||
|
|
||||||
|
class AboutPage extends ConsumerStatefulWidget {
|
||||||
|
const AboutPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState createState() => _AboutPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AboutPageState extends ConsumerState<AboutPage> {
|
||||||
|
String desc = "";
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getInfo();
|
||||||
|
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() async {
|
||||||
|
String? result = await UpdateUtils().checkUpdate(true);
|
||||||
|
if (result != null && result.isNotEmpty) {
|
||||||
|
UpdateDialog updateDialog = UpdateDialog(context, title: "发现新版本", updateContent: "版本号:v${result}", onUpdate: () {
|
||||||
|
UpdateUtils.launchURL(result);
|
||||||
|
});
|
||||||
|
updateDialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: QlAppBar(
|
||||||
|
canBack: true,
|
||||||
|
title: "关于",
|
||||||
|
),
|
||||||
|
body: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height / 10,
|
||||||
|
),
|
||||||
|
Image.asset(
|
||||||
|
"assets/images/ql.png",
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"青龙",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Version ${desc}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_launchURL("https://t.me/qinglongapp");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"Telegram频道",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_launchURL("https://github.com/qinglong-app/qinglong_app/releases");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"版本更新",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_launchURL("https://github.com/qinglong-app/qinglong_app");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"项目地址",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _launchURL(String _url) async {
|
||||||
|
try {
|
||||||
|
await launch(_url);
|
||||||
|
} catch (e) {
|
||||||
|
logger.e(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getInfo() async {
|
||||||
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
|
String version = packageInfo.version;
|
||||||
|
|
||||||
|
desc = version;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
316
lib/module/others/change_account_page.dart
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
|
import 'package:qinglong_app/base/routes.dart';
|
||||||
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
import 'package:qinglong_app/utils/login_helper.dart';
|
||||||
|
|
||||||
|
import '../../main.dart';
|
||||||
|
|
||||||
|
class ChangeAccountPage extends ConsumerStatefulWidget {
|
||||||
|
const ChangeAccountPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChangeAccountPageState createState() => _ChangeAccountPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChangeAccountPageState extends ConsumerState<ChangeAccountPage> {
|
||||||
|
String isLoginingHost = "";
|
||||||
|
String preHost = "";
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
preHost = getIt<UserInfoViewModel>().historyAccounts.first.host ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: QlAppBar(
|
||||||
|
canBack: true,
|
||||||
|
title: "切换账号",
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height / 10,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"轻触账号以切换登录",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
|
fontSize: 22,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == getIt<UserInfoViewModel>().historyAccounts.length) {
|
||||||
|
return addAccount();
|
||||||
|
}
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: Container(
|
||||||
|
color: ref.watch(themeProvider).themeColor.settingBordorColor(),
|
||||||
|
child: buildCell(index),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, __) {
|
||||||
|
return const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: getIt<UserInfoViewModel>().historyAccounts.length + 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildCell(int index) {
|
||||||
|
Widget child = ListTile(
|
||||||
|
title: Text(
|
||||||
|
getIt<UserInfoViewModel>().historyAccounts[index].host ?? "",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
minVerticalPadding: 10,
|
||||||
|
subtitle: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 10.0),
|
||||||
|
child: Text(
|
||||||
|
getIt<UserInfoViewModel>().historyAccounts[index].userName ?? "",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: index == 0
|
||||||
|
? Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(color: ref.watch(themeProvider).primaryColor, width: 1),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"已登录",
|
||||||
|
style: TextStyle(color: ref.watch(themeProvider).primaryColor, fontSize: 12),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: (isLoginingHost.isNotEmpty
|
||||||
|
? SizedBox(
|
||||||
|
width: 15,
|
||||||
|
height: 15,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getIt<UserInfoViewModel>().historyAccounts[index].host == getIt<UserInfoViewModel>().host) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Slidable(
|
||||||
|
key: ValueKey(index),
|
||||||
|
endActionPane: ActionPane(
|
||||||
|
motion: const ScrollMotion(),
|
||||||
|
extentRatio: 0.15,
|
||||||
|
children: [
|
||||||
|
SlidableAction(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
flex: 1,
|
||||||
|
onPressed: (_) {
|
||||||
|
getIt<UserInfoViewModel>().removeHistoryAccount(getIt<UserInfoViewModel>().historyAccounts[index].host);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
icon: CupertinoIcons.delete,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
loginNewByBean(getIt<UserInfoViewModel>().historyAccounts[index]);
|
||||||
|
},
|
||||||
|
child: child),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoginHelper? helper;
|
||||||
|
|
||||||
|
void loginNewByBean(UserInfoBean historyAccount) async {
|
||||||
|
isLoginingHost = historyAccount.host ?? "";
|
||||||
|
setState(() {});
|
||||||
|
helper = LoginHelper(
|
||||||
|
historyAccount.useSecretLogined,
|
||||||
|
historyAccount.host ?? "",
|
||||||
|
historyAccount.userName ?? "",
|
||||||
|
historyAccount.password ?? "",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
var response = await helper!.login();
|
||||||
|
dealLoginResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dealLoginResponse(int response) {
|
||||||
|
if (response == LoginHelper.success) {
|
||||||
|
Navigator.of(context).pushNamedAndRemoveUntil(Routes.routeHomePage, (_) => false);
|
||||||
|
} else if (response == LoginHelper.failed) {
|
||||||
|
loginFailed();
|
||||||
|
} else {
|
||||||
|
twoFact();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loginFailed() {
|
||||||
|
isLoginingHost = "";
|
||||||
|
Http.clear();
|
||||||
|
getIt<UserInfoViewModel>().updateHost(preHost);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void twoFact() {
|
||||||
|
String twoFact = "";
|
||||||
|
showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => CupertinoAlertDialog(
|
||||||
|
title: const Text("两步验证"),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
twoFact = value;
|
||||||
|
},
|
||||||
|
maxLines: 1,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "请输入code",
|
||||||
|
),
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
if (helper != null) {
|
||||||
|
var response = await helper!.loginTwice(twoFact);
|
||||||
|
dealLoginResponse(response);
|
||||||
|
} else {
|
||||||
|
"状态异常".toast();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)).then((value) {
|
||||||
|
if (value == null) {
|
||||||
|
isLoginingHost = "";
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget addAccount() {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||||
|
Routes.routeLogin,
|
||||||
|
(_) => false,
|
||||||
|
arguments: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 15,
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ref.watch(themeProvider).themeColor.settingBordorColor(),
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(
|
||||||
|
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
CupertinoIcons.add,
|
||||||
|
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"添加账号",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:dio_log/dio_log.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/http/api.dart';
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ class DependencyBean {
|
|||||||
|
|
||||||
DependencyBean.fromJson(Map<String, dynamic> json) {
|
DependencyBean.fromJson(Map<String, dynamic> json) {
|
||||||
sId = json['_id'];
|
sId = json['_id'];
|
||||||
created = json['created'];
|
created = int.tryParse(json['created'].toString());
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
timestamp = json['timestamp'];
|
timestamp = json['timestamp'].toString();
|
||||||
name = json['name'];
|
name = json['name'];
|
||||||
log = json['log'].cast<String>();
|
log = json['log'].cast<String>();
|
||||||
remark = json['remark'];
|
remark = json['remark'];
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -231,7 +233,7 @@ class DependencyCell extends ConsumerWidget {
|
|||||||
bean.status == 1
|
bean.status == 1
|
||||||
? Icon(
|
? Icon(
|
||||||
CupertinoIcons.checkmark_circle,
|
CupertinoIcons.checkmark_circle,
|
||||||
color: primaryColor,
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
size: 16,
|
size: 16,
|
||||||
)
|
)
|
||||||
: (bean.status == 2
|
: (bean.status == 2
|
||||||
@@ -302,6 +304,8 @@ class DependencyCell extends ConsumerWidget {
|
|||||||
: CupertinoScrollbar(
|
: CupertinoScrollbar(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
bean.log!.join("\n"),
|
bean.log!.join("\n"),
|
||||||
|
selectionHeightStyle: BoxHeightStyle.max,
|
||||||
|
selectionWidthStyle: BoxWidthStyle.max,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -334,13 +338,23 @@ class DependencyCell extends ConsumerWidget {
|
|||||||
content: Text("确认删除依赖 ${bean.name ?? ""} 吗"),
|
content: Text("确认删除依赖 ${bean.name ?? ""} 吗"),
|
||||||
actions: [
|
actions: [
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("取消"),
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("确定"),
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
ref.read(dependencyProvider).del(type.name.toLowerCase(), sId ?? "");
|
ref.read(dependencyProvider).del(type.name.toLowerCase(), sId ?? "");
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class LoginLogBean {
|
|||||||
{this.timestamp, this.address, this.ip, this.platform, this.status});
|
{this.timestamp, this.address, this.ip, this.platform, this.status});
|
||||||
|
|
||||||
LoginLogBean.fromJson(Map<String, dynamic> json) {
|
LoginLogBean.fromJson(Map<String, dynamic> json) {
|
||||||
timestamp = json['timestamp'];
|
timestamp = int.tryParse(json['timestamp'].toString());
|
||||||
address = json['address'];
|
address = json['address'];
|
||||||
ip = json['ip'];
|
ip = json['ip'];
|
||||||
platform = json['platform'];
|
platform = json['platform'];
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ class LoginLogPage extends ConsumerStatefulWidget {
|
|||||||
_LoginLogPageState createState() => _LoginLogPageState();
|
_LoginLogPageState createState() => _LoginLogPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginLogPageState extends ConsumerState<LoginLogPage> with LazyLoadState<LoginLogPage> {
|
class _LoginLogPageState extends ConsumerState<LoginLogPage>
|
||||||
|
with LazyLoadState<LoginLogPage> {
|
||||||
List<LoginLogBean> list = [];
|
List<LoginLogBean> list = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -32,73 +33,75 @@ class _LoginLogPageState extends ConsumerState<LoginLogPage> with LazyLoadState<
|
|||||||
backCall: () {
|
backCall: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
title: "任务日志",
|
title: "登录日志",
|
||||||
),
|
),
|
||||||
body: list.isEmpty
|
body: list.isEmpty
|
||||||
? const Center(
|
? const Center(
|
||||||
child: CupertinoActivityIndicator(),
|
child: CupertinoActivityIndicator(),
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
LoginLogBean item = list[index];
|
LoginLogBean item = list[index];
|
||||||
|
|
||||||
return ColoredBox(
|
return ColoredBox(
|
||||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
isThreeLine: true,
|
isThreeLine: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
Utils.formatMessageTime(item.timestamp ?? 0),
|
Utils.formatMessageTime(item.timestamp ?? 0),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
subtitle: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 5,
|
|
||||||
),
|
|
||||||
SelectableText(
|
|
||||||
"${item.address}",
|
|
||||||
selectionWidthStyle: BoxWidthStyle.max,
|
|
||||||
selectionHeightStyle: BoxHeightStyle.max,
|
|
||||||
style: TextStyle(
|
|
||||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
),
|
||||||
),
|
subtitle: Column(
|
||||||
const SizedBox(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: 5,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
SelectableText(
|
const SizedBox(
|
||||||
"${item.ip}",
|
height: 5,
|
||||||
selectionWidthStyle: BoxWidthStyle.max,
|
),
|
||||||
selectionHeightStyle: BoxHeightStyle.max,
|
SelectableText(
|
||||||
style: TextStyle(
|
"${item.address}",
|
||||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
selectionWidthStyle: BoxWidthStyle.max,
|
||||||
fontSize: 14,
|
selectionHeightStyle: BoxHeightStyle.max,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
ref.watch(themeProvider).themeColor.descColor(),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 5,
|
||||||
|
),
|
||||||
|
SelectableText(
|
||||||
|
"${item.ip}",
|
||||||
|
selectionWidthStyle: BoxWidthStyle.max,
|
||||||
|
selectionHeightStyle: BoxHeightStyle.max,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
ref.watch(themeProvider).themeColor.descColor(),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
trailing: item.status == 0
|
||||||
|
? Icon(
|
||||||
|
CupertinoIcons.checkmark_circle,
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
size: 16,
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
CupertinoIcons.clear_circled,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
},
|
||||||
trailing: item.status == 0
|
itemCount: list.length,
|
||||||
? Icon(
|
|
||||||
CupertinoIcons.checkmark_circle,
|
|
||||||
color: primaryColor,
|
|
||||||
size: 16,
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
CupertinoIcons.clear_circled,
|
|
||||||
color: Colors.red,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
itemCount: list.length,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +109,9 @@ class _LoginLogPageState extends ConsumerState<LoginLogPage> with LazyLoadState<
|
|||||||
HttpResponse<List<LoginLogBean>> response = await Api.loginLog();
|
HttpResponse<List<LoginLogBean>> response = await Api.loginLog();
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
if (response.bean == null || response.bean!.isEmpty) {
|
||||||
|
"暂无数据".toast();
|
||||||
|
}
|
||||||
list.clear();
|
list.clear();
|
||||||
list.addAll(response.bean ?? []);
|
list.addAll(response.bean ?? []);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:qinglong_app/base/theme.dart';
|
|||||||
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
import 'package:qinglong_app/main.dart';
|
import 'package:qinglong_app/main.dart';
|
||||||
import 'package:qinglong_app/utils/extension.dart';
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
|
||||||
class OtherPage extends ConsumerStatefulWidget {
|
class OtherPage extends ConsumerStatefulWidget {
|
||||||
const OtherPage({Key? key}) : super(key: key);
|
const OtherPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@@ -26,10 +27,10 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.symmetric(
|
||||||
horizontal: 15,
|
horizontal: 15,
|
||||||
vertical: 30,
|
vertical: 15,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
color: ref.watch(themeProvider).themeColor.settingBordorColor(),
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(
|
||||||
Radius.circular(15),
|
Radius.circular(15),
|
||||||
),
|
),
|
||||||
@@ -47,8 +48,8 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 12,
|
top: 10,
|
||||||
bottom: 8,
|
bottom: 5,
|
||||||
left: 15,
|
left: 15,
|
||||||
right: 15,
|
right: 15,
|
||||||
),
|
),
|
||||||
@@ -57,10 +58,7 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
Text(
|
Text(
|
||||||
"脚本管理",
|
"脚本管理",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: ref
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
.watch(themeProvider)
|
|
||||||
.themeColor
|
|
||||||
.titleColor(),
|
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -85,7 +83,7 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 5,
|
||||||
horizontal: 15,
|
horizontal: 15,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -93,10 +91,7 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
Text(
|
Text(
|
||||||
"依赖管理",
|
"依赖管理",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: ref
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
.watch(themeProvider)
|
|
||||||
.themeColor
|
|
||||||
.titleColor(),
|
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -120,19 +115,18 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.only(
|
||||||
vertical: 8,
|
top: 5,
|
||||||
horizontal: 15,
|
bottom: 5,
|
||||||
|
left: 15,
|
||||||
|
right: 15,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"任务日志",
|
"任务日志",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: ref
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
.watch(themeProvider)
|
|
||||||
.themeColor
|
|
||||||
.titleColor(),
|
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -160,9 +154,11 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.only(
|
||||||
vertical: 10,
|
top: 5,
|
||||||
horizontal: 15,
|
bottom: 5,
|
||||||
|
left: 15,
|
||||||
|
right: 15,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
@@ -170,10 +166,48 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
Text(
|
Text(
|
||||||
"登录日志",
|
"登录日志",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: ref
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
.watch(themeProvider)
|
fontSize: 16,
|
||||||
.themeColor
|
),
|
||||||
.titleColor(),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons.right_chevron,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(
|
||||||
|
indent: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
||||||
|
"使用client_id方式登录无法修改密码".toast();
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
Routes.routeUpdatePassword,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 5,
|
||||||
|
bottom: 10,
|
||||||
|
left: 15,
|
||||||
|
right: 15,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"修改密码",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -192,9 +226,10 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.symmetric(
|
||||||
horizontal: 15,
|
horizontal: 15,
|
||||||
|
vertical: 15,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
color: ref.watch(themeProvider).themeColor.settingBordorColor(),
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(
|
||||||
Radius.circular(15),
|
Radius.circular(15),
|
||||||
),
|
),
|
||||||
@@ -205,10 +240,9 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 5,
|
|
||||||
bottom: 5,
|
|
||||||
left: 15,
|
left: 15,
|
||||||
right: 15,
|
right: 15,
|
||||||
|
top: 5,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@@ -217,20 +251,128 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
Text(
|
Text(
|
||||||
"夜间模式",
|
"夜间模式",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
ref.watch(themeProvider).themeColor.titleColor(),
|
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
CupertinoSwitch(
|
CupertinoSwitch(
|
||||||
value: ref.watch(themeProvider).isInDartMode(),
|
value: ref.watch(themeProvider).isInDartMode(),
|
||||||
onChanged: (open) {
|
onChanged: (open) {
|
||||||
ref.watch(themeProvider).changeThemeReal(open);
|
ref.watch(themeProvider).changeThemeReal(open);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const Divider(
|
||||||
|
indent: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
Routes.routeTheme,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"主题设置",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons.right_chevron,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(
|
||||||
|
indent: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pushNamed(Routes.routeChangeAccount);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 15,
|
||||||
|
right: 15,
|
||||||
|
top: 5,
|
||||||
|
bottom: 5,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"切换账号",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons.right_chevron,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(
|
||||||
|
indent: 15,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
Routes.routeAbout,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 15,
|
||||||
|
right: 15,
|
||||||
|
top: 5,
|
||||||
|
bottom: 10,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"关于",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons.right_chevron,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -255,20 +397,29 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
showCupertinoDialog(
|
showCupertinoDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CupertinoAlertDialog(
|
builder: (context) => CupertinoAlertDialog(
|
||||||
title: const Text("确定退出登录吗?"),
|
title: const Text("确认退出登录吗?"),
|
||||||
actions: [
|
actions: [
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("取消"),
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("确定"),
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
getIt<UserInfoViewModel>().updateToken("");
|
getIt<UserInfoViewModel>().updateToken("");
|
||||||
Navigator.of(context)
|
Navigator.of(context).pushReplacementNamed(Routes.routeLogin);
|
||||||
.pushReplacementNamed(Routes.routeLogin);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -277,6 +428,9 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,24 +5,14 @@ import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
|||||||
class ScriptBean {
|
class ScriptBean {
|
||||||
String? title;
|
String? title;
|
||||||
String? value;
|
String? value;
|
||||||
String? key;
|
|
||||||
double? mtime;
|
|
||||||
bool? disabled;
|
bool? disabled;
|
||||||
List<ScriptChildren>? children;
|
List<ScriptChildren>? children;
|
||||||
|
|
||||||
ScriptBean(
|
ScriptBean({this.title, this.value, this.disabled, this.children});
|
||||||
{this.title,
|
|
||||||
this.value,
|
|
||||||
this.key,
|
|
||||||
this.mtime,
|
|
||||||
this.disabled,
|
|
||||||
this.children});
|
|
||||||
|
|
||||||
ScriptBean.fromJson(Map<String, dynamic> json) {
|
ScriptBean.fromJson(Map<String, dynamic> json) {
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
value = json['value'];
|
value = json['value'];
|
||||||
key = json['key'];
|
|
||||||
mtime = json['mtime'];
|
|
||||||
disabled = json['disabled'];
|
disabled = json['disabled'];
|
||||||
if (json['children'] != null) {
|
if (json['children'] != null) {
|
||||||
children = <ScriptChildren>[];
|
children = <ScriptChildren>[];
|
||||||
@@ -36,8 +26,6 @@ class ScriptBean {
|
|||||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
data['title'] = this.title;
|
data['title'] = this.title;
|
||||||
data['value'] = this.value;
|
data['value'] = this.value;
|
||||||
data['key'] = this.key;
|
|
||||||
data['mtime'] = this.mtime;
|
|
||||||
data['disabled'] = this.disabled;
|
data['disabled'] = this.disabled;
|
||||||
if (this.children != null) {
|
if (this.children != null) {
|
||||||
data['children'] = this.children!.map((v) => v.toJson()).toList();
|
data['children'] = this.children!.map((v) => v.toJson()).toList();
|
||||||
@@ -53,17 +41,13 @@ class ScriptBean {
|
|||||||
class ScriptChildren {
|
class ScriptChildren {
|
||||||
String? title;
|
String? title;
|
||||||
String? value;
|
String? value;
|
||||||
String? key;
|
|
||||||
double? mtime;
|
|
||||||
String? parent;
|
String? parent;
|
||||||
|
|
||||||
ScriptChildren({this.title, this.value, this.key, this.mtime, this.parent});
|
ScriptChildren({this.title, this.value, this.parent});
|
||||||
|
|
||||||
ScriptChildren.fromJson(Map<String, dynamic> json) {
|
ScriptChildren.fromJson(Map<String, dynamic> json) {
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
value = json['value'];
|
value = json['value'];
|
||||||
key = json['key'];
|
|
||||||
mtime = json['mtime'];
|
|
||||||
parent = json['parent'];
|
parent = json['parent'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,8 +55,6 @@ class ScriptChildren {
|
|||||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
data['title'] = this.title;
|
data['title'] = this.title;
|
||||||
data['value'] = this.value;
|
data['value'] = this.value;
|
||||||
data['key'] = this.key;
|
|
||||||
data['mtime'] = this.mtime;
|
|
||||||
data['parent'] = this.parent;
|
data['parent'] = this.parent;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_highlight/flutter_highlight.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/http/api.dart';
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
import 'package:qinglong_app/base/http/http.dart';
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
|
import 'package:qinglong_app/base/routes.dart';
|
||||||
import 'package:qinglong_app/base/theme.dart';
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:qinglong_app/base/ui/lazy_load_state.dart';
|
import 'package:qinglong_app/base/ui/lazy_load_state.dart';
|
||||||
import 'package:qinglong_app/utils/extension.dart';
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
import '../../../base/ui/syntax_highlighter.dart';
|
||||||
|
import '../../config/config_page.dart';
|
||||||
|
|
||||||
/// @author NewTab
|
/// @author NewTab
|
||||||
class ScriptDetailPage extends ConsumerStatefulWidget {
|
class ScriptDetailPage extends ConsumerStatefulWidget {
|
||||||
@@ -24,9 +31,120 @@ class ScriptDetailPage extends ConsumerStatefulWidget {
|
|||||||
_ScriptDetailPageState createState() => _ScriptDetailPageState();
|
_ScriptDetailPageState createState() => _ScriptDetailPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ScriptDetailPageState extends ConsumerState<ScriptDetailPage> with LazyLoadState<ScriptDetailPage> {
|
class _ScriptDetailPageState extends ConsumerState<ScriptDetailPage>
|
||||||
|
with LazyLoadState<ScriptDetailPage> {
|
||||||
String? content;
|
String? content;
|
||||||
|
|
||||||
|
List<Widget> actions = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
actions.addAll(
|
||||||
|
[
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
if (content == null || content!.isEmpty) {
|
||||||
|
"未获取到脚本内容,请稍候重试".toast();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
Routes.routeScriptUpdate,
|
||||||
|
arguments: {
|
||||||
|
"title": widget.title,
|
||||||
|
"path": widget.path,
|
||||||
|
"content": content,
|
||||||
|
},
|
||||||
|
).then((value) {
|
||||||
|
if (value != null && value == true) {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 15,
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
"编辑",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CupertinoAlertDialog(
|
||||||
|
title: const Text("确认删除"),
|
||||||
|
content: const Text("确认删除该脚本吗"),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
HttpResponse<NullResponse> result =
|
||||||
|
await Api.delScript(widget.title, widget.path ?? "");
|
||||||
|
if (result.success) {
|
||||||
|
"删除成功".toast();
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
} else {
|
||||||
|
result.message?.toast();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 15,
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
"删除",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -36,22 +154,74 @@ class _ScriptDetailPageState extends ConsumerState<ScriptDetailPage> with LazyLo
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
title: "脚本详情",
|
title: "脚本详情",
|
||||||
),
|
actions: [
|
||||||
body:content == null
|
InkWell(
|
||||||
? const Center(
|
onTap: () {
|
||||||
child: CupertinoActivityIndicator(),
|
showCupertinoModalPopup(
|
||||||
)
|
context: context,
|
||||||
: SingleChildScrollView(
|
builder: (context) {
|
||||||
child: HighlightView(
|
return CupertinoActionSheet(
|
||||||
content ?? "",
|
title: Container(
|
||||||
language: getLanguageType(widget.title),
|
alignment: Alignment.center,
|
||||||
padding: const EdgeInsets.symmetric(
|
child: const Material(
|
||||||
horizontal: 15,
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
"更多操作",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: actions,
|
||||||
|
cancelButton: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
child: const Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.more_horiz,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 26,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
theme: ref.watch(themeProvider).themeColor.codeEditorTheme(),
|
],
|
||||||
tabSize: 14,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
body: content == null
|
||||||
|
? const Center(
|
||||||
|
child: CupertinoActivityIndicator(),
|
||||||
|
)
|
||||||
|
: CodeWidget(
|
||||||
|
content: content ?? "",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
93
lib/module/others/scripts/script_edit_page.dart
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
|
import 'package:qinglong_app/module/config/config_viewmodel.dart';
|
||||||
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
|
||||||
|
class ScriptEditPage extends ConsumerStatefulWidget {
|
||||||
|
final String content;
|
||||||
|
final String title;
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
const ScriptEditPage(this.title, this.path, this.content, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ScriptEditPageState createState() => _ScriptEditPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScriptEditPageState extends ConsumerState<ScriptEditPage> {
|
||||||
|
late TextEditingController _controller;
|
||||||
|
FocusNode node = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_controller = TextEditingController(text: widget.content);
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback(
|
||||||
|
(timeStamp) {
|
||||||
|
node.requestFocus();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: QlAppBar(
|
||||||
|
canBack: true,
|
||||||
|
backCall: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
title: '编辑${widget.title}',
|
||||||
|
actions: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
HttpResponse<NullResponse> response = await Api.updateScript(widget.title, widget.path, _controller.text);
|
||||||
|
if (response.success) {
|
||||||
|
"提交成功".toast();
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
} else {
|
||||||
|
(response.message ?? "").toast();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"提交",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 15,
|
||||||
|
right: 15,
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: node,
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.read(themeProvider).themeColor.descColor(),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
controller: _controller,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,7 +60,11 @@ class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<Scri
|
|||||||
"title": e.title,
|
"title": e.title,
|
||||||
"path": e.parent,
|
"path": e.parent,
|
||||||
},
|
},
|
||||||
);
|
).then((value) {
|
||||||
|
if (value != null && value == true) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
title: Text(
|
title: Text(
|
||||||
e.title ?? "",
|
e.title ?? "",
|
||||||
@@ -82,6 +86,12 @@ class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<Scri
|
|||||||
"title": item.title,
|
"title": item.title,
|
||||||
"path": "",
|
"path": "",
|
||||||
},
|
},
|
||||||
|
).then(
|
||||||
|
(value) {
|
||||||
|
if (value != null && value == true) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -104,6 +114,9 @@ class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<Scri
|
|||||||
HttpResponse<List<ScriptBean>> response = await Api.scripts();
|
HttpResponse<List<ScriptBean>> response = await Api.scripts();
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
if (response.bean == null || response.bean!.isEmpty) {
|
||||||
|
"暂无数据".toast();
|
||||||
|
}
|
||||||
list.clear();
|
list.clear();
|
||||||
list.addAll(response.bean ?? []);
|
list.addAll(response.bean ?? []);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -41,6 +43,8 @@ class _TaskLogDetailPageState extends ConsumerState<TaskLogDetailPage> with Lazy
|
|||||||
),
|
),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
(content == null || content!.isEmpty) ? "暂无数据" : content!,
|
(content == null || content!.isEmpty) ? "暂无数据" : content!,
|
||||||
|
selectionHeightStyle: BoxHeightStyle.max,
|
||||||
|
selectionWidthStyle: BoxWidthStyle.max,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ class _TaskLogPageState extends ConsumerState<TaskLogPage> with LazyLoadState<Ta
|
|||||||
HttpResponse<List<TaskLogBean>> response = await Api.taskLog();
|
HttpResponse<List<TaskLogBean>> response = await Api.taskLog();
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
if (response.bean == null || response.bean!.isEmpty) {
|
||||||
|
"暂无数据".toast();
|
||||||
|
}
|
||||||
list.clear();
|
list.clear();
|
||||||
list.addAll(response.bean ?? []);
|
list.addAll(response.bean ?? []);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|||||||
478
lib/module/others/theme_page.dart
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
|
|
||||||
|
class ThemePage extends ConsumerStatefulWidget {
|
||||||
|
const ThemePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ThemePageState createState() => _ThemePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ThemePageState extends ConsumerState<ThemePage> {
|
||||||
|
late Color _primaryColor;
|
||||||
|
List<Color> colors = [];
|
||||||
|
double colorHeight = 40;
|
||||||
|
double smallColorHeight = 8;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_primaryColor = ref.read(themeProvider).primaryColor;
|
||||||
|
colors.add(commonColor);
|
||||||
|
colors.addAll([
|
||||||
|
Colors.red,
|
||||||
|
Colors.pink,
|
||||||
|
Colors.purple,
|
||||||
|
Colors.deepPurple,
|
||||||
|
Colors.indigo,
|
||||||
|
Colors.blue,
|
||||||
|
Colors.lightBlue,
|
||||||
|
Colors.cyan,
|
||||||
|
Colors.teal,
|
||||||
|
Colors.green,
|
||||||
|
Colors.lightGreen,
|
||||||
|
Colors.lime,
|
||||||
|
Colors.yellow,
|
||||||
|
Colors.amber,
|
||||||
|
Colors.orange,
|
||||||
|
Colors.deepOrange,
|
||||||
|
Colors.brown,
|
||||||
|
Colors.blueGrey,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xff999999),
|
||||||
|
appBar: QlAppBar(
|
||||||
|
title: '主题设置',
|
||||||
|
canBack: true,
|
||||||
|
actions: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
submit();
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"保存",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
bottom: true,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SizedBox(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).size.height * 3 / 4 - kToolbarHeight,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: _primaryColor,
|
||||||
|
title: Text("示例页面"),
|
||||||
|
elevation: 0,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: ListView.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
color: index == 0
|
||||||
|
? ref
|
||||||
|
.watch(themeProvider)
|
||||||
|
.themeColor
|
||||||
|
.pinColor()
|
||||||
|
: Colors.transparent,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Random().nextBool()
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: SizedBox(
|
||||||
|
width: 15,
|
||||||
|
height: 15,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: _primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: Random().nextBool()
|
||||||
|
? 0
|
||||||
|
: 5,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
"示例名称",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow:
|
||||||
|
TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow
|
||||||
|
.ellipsis,
|
||||||
|
color: ref
|
||||||
|
.watch(
|
||||||
|
themeProvider)
|
||||||
|
.themeColor
|
||||||
|
.titleColor(),
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
"上午10:00",
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: ref
|
||||||
|
.watch(themeProvider)
|
||||||
|
.themeColor
|
||||||
|
.descColor(),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Random().nextBool()
|
||||||
|
? const Icon(
|
||||||
|
Icons.dnd_forwardslash,
|
||||||
|
size: 12,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
const SizedBox(
|
||||||
|
width: 5,
|
||||||
|
),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
"10 1-12/2 * * *",
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: ref
|
||||||
|
.watch(themeProvider)
|
||||||
|
.themeColor
|
||||||
|
.descColor(),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
"task raw post.js",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: ref
|
||||||
|
.watch(themeProvider)
|
||||||
|
.themeColor
|
||||||
|
.descColor(),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(
|
||||||
|
height: 1,
|
||||||
|
indent: 15,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
height: colorHeight + 20,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: ListView.separated(
|
||||||
|
physics: BouncingScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == 0) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
pickColor();
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: colorHeight,
|
||||||
|
height: colorHeight,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
width: smallColorHeight,
|
||||||
|
height: smallColorHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[0],
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(smallColorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
left: 0,
|
||||||
|
top: colorHeight / 4 - smallColorHeight / 2,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
width: smallColorHeight,
|
||||||
|
height: smallColorHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[1],
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(smallColorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
left: 0,
|
||||||
|
bottom: colorHeight / 4 - smallColorHeight / 2,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
width: smallColorHeight,
|
||||||
|
height: smallColorHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[2],
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(smallColorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
left: colorHeight / 2 - smallColorHeight / 2,
|
||||||
|
top: 0,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
width: smallColorHeight,
|
||||||
|
height: smallColorHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[3],
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(smallColorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
right: 0,
|
||||||
|
top: colorHeight / 4 - smallColorHeight / 2,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
width: smallColorHeight,
|
||||||
|
height: smallColorHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[4],
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(smallColorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
right: 0,
|
||||||
|
bottom: colorHeight / 4 - smallColorHeight / 2,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
width: smallColorHeight,
|
||||||
|
height: smallColorHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[5],
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(smallColorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
left: colorHeight / 2 - smallColorHeight / 2,
|
||||||
|
bottom: 0,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
width: smallColorHeight,
|
||||||
|
height: smallColorHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[6],
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(smallColorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
left: colorHeight / 2 - smallColorHeight / 2,
|
||||||
|
bottom: 0,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
width: smallColorHeight,
|
||||||
|
height: smallColorHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[7],
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(smallColorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
left: colorHeight / 2 - smallColorHeight / 2,
|
||||||
|
bottom: colorHeight / 2 - smallColorHeight / 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
index = index - 1;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_primaryColor = colors[index];
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: colorHeight,
|
||||||
|
height: colorHeight,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors[index],
|
||||||
|
borderRadius: BorderRadius.circular(colorHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemCount: colors.length + 1,
|
||||||
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
|
return const SizedBox(
|
||||||
|
width: 15,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pickColor() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
titlePadding: const EdgeInsets.all(0),
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ColorPicker(
|
||||||
|
pickerColor: _primaryColor,
|
||||||
|
onColorChanged: (color) {
|
||||||
|
_primaryColor = color;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
colorPickerWidth: 300,
|
||||||
|
pickerAreaHeightPercent: 0.7,
|
||||||
|
enableAlpha: false,
|
||||||
|
pickerAreaBorderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(2),
|
||||||
|
topRight: Radius.circular(2),
|
||||||
|
),
|
||||||
|
hexInputBar: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void submit() {
|
||||||
|
ref.read(themeProvider).changePrimaryColor(_primaryColor);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
212
lib/module/others/update_password_page.dart
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
|
import 'package:qinglong_app/base/routes.dart';
|
||||||
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
|
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||||
|
import 'package:qinglong_app/module/task/task_viewmodel.dart';
|
||||||
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
|
||||||
|
import '../../main.dart';
|
||||||
|
|
||||||
|
class UpdatePasswordPage extends ConsumerStatefulWidget {
|
||||||
|
const UpdatePasswordPage({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<UpdatePasswordPage> createState() => _UpdatePasswordPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdatePasswordPageState extends ConsumerState<UpdatePasswordPage> {
|
||||||
|
final TextEditingController _nameController = TextEditingController();
|
||||||
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
|
final TextEditingController _passwordAgainController =
|
||||||
|
TextEditingController();
|
||||||
|
|
||||||
|
FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback(
|
||||||
|
(timeStamp) {
|
||||||
|
focusNode.requestFocus();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: QlAppBar(
|
||||||
|
canBack: true,
|
||||||
|
backCall: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
title: "修改用户名密码",
|
||||||
|
actions: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
submit();
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"提交",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"用户名:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
focusNode: focusNode,
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "请输入用户名",
|
||||||
|
),
|
||||||
|
autofocus: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"新密码:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
obscureText: true,
|
||||||
|
controller: _passwordController,
|
||||||
|
maxLines: 1,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "请输入新密码",
|
||||||
|
),
|
||||||
|
autofocus: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"再次输入新密码:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
obscureText: true,
|
||||||
|
maxLines: 1,
|
||||||
|
controller: _passwordAgainController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||||
|
hintText: "再次输入新密码",
|
||||||
|
),
|
||||||
|
autofocus: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void submit() async {
|
||||||
|
if (_nameController.text.isEmpty) {
|
||||||
|
"用户名不能为空".toast();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_passwordController.text.isEmpty ||
|
||||||
|
_passwordAgainController.text.isEmpty) {
|
||||||
|
"密码不能为空".toast();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_passwordAgainController.text != _passwordController.text) {
|
||||||
|
"两次输入的密码不一致".toast();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commitReal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void commitReal() async {
|
||||||
|
String name = _nameController.text;
|
||||||
|
String password = _passwordController.text;
|
||||||
|
HttpResponse<NullResponse> response =
|
||||||
|
await Api.updatePassword(name, password);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
"更新成功".toast();
|
||||||
|
|
||||||
|
if (!getIt<UserInfoViewModel>().useSecretLogined) {
|
||||||
|
getIt<UserInfoViewModel>().updateUserName(
|
||||||
|
getIt<UserInfoViewModel>().host ?? "", name, password, false);
|
||||||
|
}
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushNamedAndRemoveUntil(Routes.routeLogin, (route) => false);
|
||||||
|
} else {
|
||||||
|
response.message.toast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -68,6 +69,8 @@ class _InTimeLogPageState extends State<InTimeLogPage> {
|
|||||||
: CupertinoScrollbar(
|
: CupertinoScrollbar(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
content!,
|
content!,
|
||||||
|
selectionHeightStyle: BoxHeightStyle.max,
|
||||||
|
selectionWidthStyle: BoxWidthStyle.max,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
||||||
|
|
||||||
|
import '../../main.dart';
|
||||||
|
|
||||||
@JsonConversion()
|
@JsonConversion()
|
||||||
class TaskBean {
|
class TaskBean {
|
||||||
String? name;
|
String? name;
|
||||||
@@ -43,20 +45,19 @@ class TaskBean {
|
|||||||
command = json['command'].toString();
|
command = json['command'].toString();
|
||||||
schedule = json['schedule'].toString();
|
schedule = json['schedule'].toString();
|
||||||
saved = json['saved'];
|
saved = json['saved'];
|
||||||
sId = json['_id'].toString();
|
sId = json.containsKey('_id') ? json['_id'].toString() : (json.containsKey('id') ? json['id'].toString() : "");
|
||||||
created = json['created'];
|
created = int.tryParse(json['created'].toString());
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
timestamp = json['timestamp'].toString();
|
timestamp = json['timestamp'].toString();
|
||||||
isSystem = json['isSystem'];
|
isSystem = json['isSystem'];
|
||||||
isDisabled = json['isDisabled'];
|
isDisabled = json['isDisabled'];
|
||||||
logPath = json['log_path'].toString();
|
logPath = json['log_path'].toString();
|
||||||
isPinned = json['isPinned'];
|
isPinned = json['isPinned'];
|
||||||
lastExecutionTime = json['last_execution_time'];
|
lastExecutionTime = int.tryParse(json['last_execution_time'].toString());
|
||||||
lastRunningTime = json['last_running_time'];
|
lastRunningTime = json['last_running_time'];
|
||||||
pid = json['pid'].toString();
|
pid = json['pid'].toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(jsonEncode(json));
|
logger.e(e);
|
||||||
print(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -152,9 +154,13 @@ class _TaskDetailPageState extends ConsumerState<TaskDetailPage> {
|
|||||||
[
|
[
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
startCron(context, ref);
|
if (widget.taskBean.status! == 1) {
|
||||||
|
await startCron(context, ref);
|
||||||
|
} else {
|
||||||
|
await stopCron(context, ref);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -367,13 +373,23 @@ class _TaskDetailPageState extends ConsumerState<TaskDetailPage> {
|
|||||||
content: Text("确认删除定时任务 ${widget.taskBean.name ?? ""} 吗"),
|
content: Text("确认删除定时任务 ${widget.taskBean.name ?? ""} 吗"),
|
||||||
actions: [
|
actions: [
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("取消"),
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("确定"),
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await ref.read(taskProvider).delCron(widget.taskBean.sId!);
|
await ref.read(taskProvider).delCron(widget.taskBean.sId!);
|
||||||
@@ -461,6 +477,8 @@ class TaskDetailCell extends ConsumerWidget {
|
|||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
desc!,
|
desc!,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
|
selectionHeightStyle: BoxHeightStyle.max,
|
||||||
|
selectionWidthStyle: BoxWidthStyle.max,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (taped != null) {
|
if (taped != null) {
|
||||||
taped!();
|
taped!();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class _TaskPageState extends State<TaskPage> {
|
|||||||
searchCell(ref),
|
searchCell(ref),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultTabController(
|
child: DefaultTabController(
|
||||||
length: 3,
|
length: 5,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -53,6 +53,12 @@ class _TaskPageState extends State<TaskPage> {
|
|||||||
Tab(
|
Tab(
|
||||||
text: "正在运行",
|
text: "正在运行",
|
||||||
),
|
),
|
||||||
|
Tab(
|
||||||
|
text: "从未运行",
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
text: "拉库脚本",
|
||||||
|
),
|
||||||
Tab(
|
Tab(
|
||||||
text: "已禁用",
|
text: "已禁用",
|
||||||
),
|
),
|
||||||
@@ -70,6 +76,8 @@ class _TaskPageState extends State<TaskPage> {
|
|||||||
children: [
|
children: [
|
||||||
body(model, model.list, ref),
|
body(model, model.list, ref),
|
||||||
body(model, model.running, ref),
|
body(model, model.running, ref),
|
||||||
|
body(model, model.neverRunning, ref),
|
||||||
|
body(model, model.notScripts, ref),
|
||||||
body(model, model.disabled, ref),
|
body(model, model.disabled, ref),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -178,7 +186,11 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
trailingIcon: bean.status! == 1 ? CupertinoIcons.memories : CupertinoIcons.stop_circle,
|
trailingIcon: bean.status! == 1 ? CupertinoIcons.memories : CupertinoIcons.stop_circle,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
startCron(context, ref);
|
if (bean.status! == 1) {
|
||||||
|
startCron(context, ref);
|
||||||
|
} else {
|
||||||
|
stopCron(context, ref);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
QLCupertinoContextMenuAction(
|
QLCupertinoContextMenuAction(
|
||||||
@@ -236,8 +248,8 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
top: 5,
|
top: 5,
|
||||||
bottom: isDark ? 5 : 20,
|
bottom: isDark ? 5 : 20,
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: SizedBox(
|
||||||
borderRadius: BorderRadius.circular(15),
|
height: 200,
|
||||||
child: TaskDetailPage(
|
child: TaskDetailPage(
|
||||||
bean,
|
bean,
|
||||||
hideAppbar: true,
|
hideAppbar: true,
|
||||||
@@ -251,6 +263,7 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
color: bean.isPinned == 1 ? ref.watch(themeProvider).themeColor.pinColor() : Colors.transparent,
|
color: bean.isPinned == 1 ? ref.watch(themeProvider).themeColor.pinColor() : Colors.transparent,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 15,
|
horizontal: 15,
|
||||||
@@ -264,31 +277,42 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Expanded(
|
||||||
color: Colors.transparent,
|
child: Row(
|
||||||
child: Text(
|
mainAxisSize: MainAxisSize.max,
|
||||||
bean.name ?? "",
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
maxLines: 1,
|
children: [
|
||||||
style: TextStyle(
|
bean.status == 1
|
||||||
overflow: TextOverflow.ellipsis,
|
? const SizedBox.shrink()
|
||||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
: SizedBox(
|
||||||
fontSize: 18,
|
width: 15,
|
||||||
),
|
height: 15,
|
||||||
),
|
child: CircularProgressIndicator(
|
||||||
),
|
strokeWidth: 2,
|
||||||
const SizedBox(
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
width: 5,
|
),
|
||||||
),
|
),
|
||||||
bean.status == 1
|
SizedBox(
|
||||||
? const SizedBox.shrink()
|
width: bean.status == 1 ? 0 : 5,
|
||||||
: const SizedBox(
|
),
|
||||||
width: 15,
|
Expanded(
|
||||||
height: 15,
|
child: Material(
|
||||||
child: CircularProgressIndicator(
|
color: Colors.transparent,
|
||||||
strokeWidth: 2,
|
child: Text(
|
||||||
|
bean.name ?? "",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Material(
|
Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -307,7 +331,18 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
bean.isDisabled == 1
|
||||||
|
? const Icon(
|
||||||
|
Icons.dnd_forwardslash,
|
||||||
|
size: 12,
|
||||||
|
color: Colors.red,
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
const SizedBox(
|
||||||
|
width: 5,
|
||||||
|
),
|
||||||
Material(
|
Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -320,18 +355,24 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
|
||||||
width: 5,
|
|
||||||
),
|
|
||||||
bean.isDisabled == 1
|
|
||||||
? const Icon(
|
|
||||||
Icons.dnd_forwardslash,
|
|
||||||
size: 12,
|
|
||||||
color: Colors.red,
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Text(
|
||||||
|
bean.command ?? "",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -401,13 +442,23 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
content: Text("确认删除定时任务 ${bean.name ?? ""} 吗"),
|
content: Text("确认删除定时任务 ${bean.name ?? ""} 吗"),
|
||||||
actions: [
|
actions: [
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("取消"),
|
child: const Text(
|
||||||
|
"取消",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xff999999),
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child: const Text("确定"),
|
child: Text(
|
||||||
|
"确定",
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.watch(themeProvider).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
ref.read(taskProvider).delCron(bean.sId!);
|
ref.read(taskProvider).delCron(bean.sId!);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ var taskProvider = ChangeNotifierProvider((ref) => TaskViewModel());
|
|||||||
class TaskViewModel extends BaseViewModel {
|
class TaskViewModel extends BaseViewModel {
|
||||||
List<TaskBean> list = [];
|
List<TaskBean> list = [];
|
||||||
List<TaskBean> running = [];
|
List<TaskBean> running = [];
|
||||||
|
List<TaskBean> neverRunning = [];
|
||||||
|
List<TaskBean> notScripts = [];
|
||||||
List<TaskBean> disabled = [];
|
List<TaskBean> disabled = [];
|
||||||
|
|
||||||
Future<void> loadData([isLoading = true]) async {
|
Future<void> loadData([isLoading = true]) async {
|
||||||
@@ -40,6 +42,10 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
running.clear();
|
running.clear();
|
||||||
running.addAll(list.where((element) => element.status == 0));
|
running.addAll(list.where((element) => element.status == 0));
|
||||||
|
neverRunning.clear();
|
||||||
|
neverRunning.addAll(list.where((element) => element.lastRunningTime == null ));
|
||||||
|
notScripts.clear();
|
||||||
|
notScripts.addAll(list.where((element) => (element.command != null && (element.command!.startsWith("ql repo") || element.command!.startsWith("ql raw")))));
|
||||||
disabled.clear();
|
disabled.clear();
|
||||||
disabled.addAll(list.where((element) => element.isDisabled == 1));
|
disabled.addAll(list.where((element) => element.isDisabled == 1));
|
||||||
}
|
}
|
||||||
@@ -48,6 +54,7 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
HttpResponse<NullResponse> result = await Api.startTasks([cron]);
|
HttpResponse<NullResponse> result = await Api.startTasks([cron]);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
list.firstWhere((element) => element.sId == cron).status = 0;
|
list.firstWhere((element) => element.sId == cron).status = 0;
|
||||||
|
sortList();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
failToast(result.message, notify: true);
|
failToast(result.message, notify: true);
|
||||||
@@ -58,6 +65,7 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
HttpResponse<NullResponse> result = await Api.stopTasks([cron]);
|
HttpResponse<NullResponse> result = await Api.stopTasks([cron]);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
list.firstWhere((element) => element.sId == cron).status = 1;
|
list.firstWhere((element) => element.sId == cron).status = 1;
|
||||||
|
sortList();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
failToast(result.message, notify: true);
|
failToast(result.message, notify: true);
|
||||||
@@ -92,6 +100,8 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
HttpResponse<NullResponse> response = await Api.unpinTask(sId);
|
HttpResponse<NullResponse> response = await Api.unpinTask(sId);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
"取消置顶成功".toast();
|
||||||
|
list.firstWhere((element) => element.sId == sId).isPinned = 0;
|
||||||
sortList();
|
sortList();
|
||||||
success();
|
success();
|
||||||
} else {
|
} else {
|
||||||
@@ -101,6 +111,8 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
HttpResponse<NullResponse> response = await Api.pinTask(sId);
|
HttpResponse<NullResponse> response = await Api.pinTask(sId);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
"置顶成功".toast();
|
||||||
|
list.firstWhere((element) => element.sId == sId).isPinned = 1;
|
||||||
sortList();
|
sortList();
|
||||||
success();
|
success();
|
||||||
} else {
|
} else {
|
||||||
@@ -114,6 +126,7 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
HttpResponse<NullResponse> response = await Api.disableTask(sId);
|
HttpResponse<NullResponse> response = await Api.disableTask(sId);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
"禁用成功".toast();
|
||||||
list.firstWhere((element) => element.sId == sId).isDisabled = 1;
|
list.firstWhere((element) => element.sId == sId).isDisabled = 1;
|
||||||
sortList();
|
sortList();
|
||||||
success();
|
success();
|
||||||
@@ -124,6 +137,7 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
HttpResponse<NullResponse> response = await Api.enableTask(sId);
|
HttpResponse<NullResponse> response = await Api.enableTask(sId);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
"启用成功".toast();
|
||||||
list.firstWhere((element) => element.sId == sId).isDisabled = 0;
|
list.firstWhere((element) => element.sId == sId).isDisabled = 0;
|
||||||
sortList();
|
sortList();
|
||||||
success();
|
success();
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
|
||||||
extension ContextExt on BuildContext {
|
extension ContextExt on BuildContext {
|
||||||
T read<T>(ProviderBase<T> provider) {
|
T read<T>(ProviderBase<T> provider) {
|
||||||
return ProviderScope.containerOf(this, listen: false).read(provider);
|
return ProviderScope.containerOf(this, listen: false).read(provider);
|
||||||
@@ -18,4 +20,8 @@ extension StringExt on String? {
|
|||||||
timeInSecForIosWeb: 1,
|
timeInSecForIosWeb: 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void log() {
|
||||||
|
logger.i(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
89
lib/utils/login_helper.dart
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
|
import 'package:qinglong_app/module/login/login_bean.dart';
|
||||||
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
|
||||||
|
class LoginHelper {
|
||||||
|
static int success = 0;
|
||||||
|
static int failed = 1;
|
||||||
|
static int twiceLogin = 2;
|
||||||
|
bool useSecretLogin = false;
|
||||||
|
|
||||||
|
final String host;
|
||||||
|
final String userName;
|
||||||
|
final String password;
|
||||||
|
final bool rememberPassword;
|
||||||
|
|
||||||
|
LoginHelper(this.useSecretLogin, this.host, this.userName, this.password,
|
||||||
|
this.rememberPassword);
|
||||||
|
|
||||||
|
Future<int> login() async {
|
||||||
|
Http.clear();
|
||||||
|
getIt<UserInfoViewModel>().updateHost(host);
|
||||||
|
HttpResponse<LoginBean> response;
|
||||||
|
|
||||||
|
if (loginByUserName()) {
|
||||||
|
response = await Api.login(userName, password);
|
||||||
|
} else {
|
||||||
|
response = await Api.loginByClientId(userName, password);
|
||||||
|
}
|
||||||
|
if (response.success) {
|
||||||
|
loginSuccess(response, userName, password);
|
||||||
|
return success;
|
||||||
|
} else if (loginByUserName() && response.code == 401) {
|
||||||
|
//可能用户使用的是老版本qinglong
|
||||||
|
HttpResponse<LoginBean> oldResponse =
|
||||||
|
await Api.loginOld(userName, password);
|
||||||
|
if (oldResponse.success) {
|
||||||
|
loginSuccess(oldResponse, userName, password);
|
||||||
|
return success;
|
||||||
|
} else {
|
||||||
|
(oldResponse.message ?? "请检查网络情况").toast();
|
||||||
|
if (oldResponse.code == 420) {
|
||||||
|
return twiceLogin;
|
||||||
|
} else {
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(response.message ?? "请检查网络情况").toast();
|
||||||
|
//420代表需要2步验证
|
||||||
|
if (response.code == 420) {
|
||||||
|
return twiceLogin;
|
||||||
|
} else {
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> loginTwice(String code) async {
|
||||||
|
HttpResponse<LoginBean> response =
|
||||||
|
await Api.loginTwo(userName, password, code);
|
||||||
|
if (response.success) {
|
||||||
|
loginSuccess(response, userName, password);
|
||||||
|
return success;
|
||||||
|
} else {
|
||||||
|
(response.message ?? "请检查网络情况").toast();
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loginSuccess(
|
||||||
|
HttpResponse<LoginBean> response, String userName, String password) {
|
||||||
|
getIt<UserInfoViewModel>().updateToken(response.bean?.token ?? "");
|
||||||
|
if (rememberPassword) {
|
||||||
|
getIt<UserInfoViewModel>()
|
||||||
|
.updateUserName(host, userName, password, !loginByUserName());
|
||||||
|
} else {
|
||||||
|
getIt<UserInfoViewModel>()
|
||||||
|
.updateUserName(host, "", "", !loginByUserName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loginByUserName() {
|
||||||
|
return !useSecretLogin;
|
||||||
|
}
|
||||||
|
}
|
||||||
399
lib/utils/update_utils.dart
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:qinglong_app/base/http/url.dart';
|
||||||
|
import 'package:qinglong_app/utils/extension.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
|
||||||
|
/// @author NewTab
|
||||||
|
|
||||||
|
class UpdateUtils {
|
||||||
|
Future<String?> checkUpdate([bool remind = false]) async {
|
||||||
|
try {
|
||||||
|
if (!Platform.isAndroid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
|
String version = packageInfo.version;
|
||||||
|
String url = Url.checkUpdateUrl;
|
||||||
|
Dio dio = Dio(BaseOptions(
|
||||||
|
connectTimeout: 50000,
|
||||||
|
receiveTimeout: 50000,
|
||||||
|
sendTimeout: 50000,
|
||||||
|
));
|
||||||
|
var response = await dio.get(url);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
if (version == response.data as String) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return response.data as String;
|
||||||
|
}
|
||||||
|
} on DioError catch (e) {
|
||||||
|
if (remind) {
|
||||||
|
"无法连接到github服务器,版本更新检测失败".toast();
|
||||||
|
}
|
||||||
|
logger.e(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void launchURL(String version) async {
|
||||||
|
try {
|
||||||
|
await launch("https://github.com/qinglong-app/qinglong_app/releases/download/v$version/app-release-v$version.apk");
|
||||||
|
} catch (e) {
|
||||||
|
logger.e(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///版本更新加提示框
|
||||||
|
class UpdateDialog {
|
||||||
|
bool _isShowing = false;
|
||||||
|
late BuildContext _context;
|
||||||
|
late UpdateWidget _widget;
|
||||||
|
|
||||||
|
UpdateDialog(BuildContext context,
|
||||||
|
{double width = 0.0,
|
||||||
|
required String title,
|
||||||
|
required String updateContent,
|
||||||
|
required VoidCallback onUpdate,
|
||||||
|
double titleTextSize = 16.0,
|
||||||
|
double contentTextSize = 14.0,
|
||||||
|
double buttonTextSize = 14.0,
|
||||||
|
double progress = -1.0,
|
||||||
|
Color progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||||
|
Image? topImage,
|
||||||
|
double extraHeight = 5.0,
|
||||||
|
double radius = 4.0,
|
||||||
|
Color themeColor = Colors.red,
|
||||||
|
bool enableIgnore = false,
|
||||||
|
VoidCallback? onIgnore,
|
||||||
|
bool isForce = false,
|
||||||
|
String? updateButtonText,
|
||||||
|
String? ignoreButtonText,
|
||||||
|
VoidCallback? onClose}) {
|
||||||
|
_context = context;
|
||||||
|
_widget = UpdateWidget(
|
||||||
|
width: width,
|
||||||
|
title: title,
|
||||||
|
updateContent: updateContent,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
titleTextSize: titleTextSize,
|
||||||
|
contentTextSize: contentTextSize,
|
||||||
|
buttonTextSize: buttonTextSize,
|
||||||
|
progress: progress,
|
||||||
|
topImage: topImage,
|
||||||
|
extraHeight: extraHeight,
|
||||||
|
radius: radius,
|
||||||
|
themeColor: themeColor,
|
||||||
|
progressBackgroundColor: progressBackgroundColor,
|
||||||
|
enableIgnore: enableIgnore,
|
||||||
|
onIgnore: onIgnore,
|
||||||
|
isForce: isForce,
|
||||||
|
updateButtonText: updateButtonText ?? '更新',
|
||||||
|
ignoreButtonText: ignoreButtonText ?? '忽略此版本',
|
||||||
|
onClose: onClose ?? () => dismiss());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 显示弹窗
|
||||||
|
Future<bool> show() {
|
||||||
|
try {
|
||||||
|
if (isShowing()) {
|
||||||
|
return Future<bool>.value(false);
|
||||||
|
}
|
||||||
|
showDialog<bool>(
|
||||||
|
context: _context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return WillPopScope(onWillPop: () => Future<bool>.value(false), child: _widget);
|
||||||
|
});
|
||||||
|
_isShowing = true;
|
||||||
|
return Future<bool>.value(true);
|
||||||
|
} catch (err) {
|
||||||
|
_isShowing = false;
|
||||||
|
return Future<bool>.value(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 隐藏弹窗
|
||||||
|
Future<bool> dismiss() {
|
||||||
|
try {
|
||||||
|
if (_isShowing) {
|
||||||
|
_isShowing = false;
|
||||||
|
Navigator.pop(_context);
|
||||||
|
return Future<bool>.value(true);
|
||||||
|
} else {
|
||||||
|
return Future<bool>.value(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return Future<bool>.value(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 是否显示
|
||||||
|
bool isShowing() {
|
||||||
|
return _isShowing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新进度
|
||||||
|
void update(double progress) {
|
||||||
|
if (isShowing()) {
|
||||||
|
_widget.update(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 显示版本更新提示框
|
||||||
|
static UpdateDialog showUpdate(BuildContext context,
|
||||||
|
{double width = 0.0,
|
||||||
|
required String title,
|
||||||
|
required String updateContent,
|
||||||
|
required VoidCallback onUpdate,
|
||||||
|
double titleTextSize = 16.0,
|
||||||
|
double contentTextSize = 14.0,
|
||||||
|
double buttonTextSize = 14.0,
|
||||||
|
double progress = -1.0,
|
||||||
|
Color progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||||
|
Image? topImage,
|
||||||
|
double extraHeight = 5.0,
|
||||||
|
double radius = 4.0,
|
||||||
|
Color themeColor = Colors.red,
|
||||||
|
bool enableIgnore = false,
|
||||||
|
VoidCallback? onIgnore,
|
||||||
|
String? updateButtonText,
|
||||||
|
String? ignoreButtonText,
|
||||||
|
bool isForce = false}) {
|
||||||
|
final UpdateDialog dialog = UpdateDialog(context,
|
||||||
|
width: width,
|
||||||
|
title: title,
|
||||||
|
updateContent: updateContent,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
titleTextSize: titleTextSize,
|
||||||
|
contentTextSize: contentTextSize,
|
||||||
|
buttonTextSize: buttonTextSize,
|
||||||
|
progress: progress,
|
||||||
|
topImage: topImage,
|
||||||
|
extraHeight: extraHeight,
|
||||||
|
radius: radius,
|
||||||
|
themeColor: themeColor,
|
||||||
|
progressBackgroundColor: progressBackgroundColor,
|
||||||
|
enableIgnore: enableIgnore,
|
||||||
|
isForce: isForce,
|
||||||
|
updateButtonText: updateButtonText,
|
||||||
|
ignoreButtonText: ignoreButtonText,
|
||||||
|
onIgnore: onIgnore);
|
||||||
|
dialog.show();
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: must_be_immutable
|
||||||
|
class UpdateWidget extends StatefulWidget {
|
||||||
|
/// 对话框的宽度
|
||||||
|
final double width;
|
||||||
|
|
||||||
|
/// 升级标题
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
/// 更新内容
|
||||||
|
final String updateContent;
|
||||||
|
|
||||||
|
/// 标题文字的大小
|
||||||
|
final double titleTextSize;
|
||||||
|
|
||||||
|
/// 更新文字内容的大小
|
||||||
|
final double contentTextSize;
|
||||||
|
|
||||||
|
/// 按钮文字的大小
|
||||||
|
final double buttonTextSize;
|
||||||
|
|
||||||
|
/// 顶部图片
|
||||||
|
final Widget? topImage;
|
||||||
|
|
||||||
|
/// 拓展高度(适配顶部图片高度不一致的情况)
|
||||||
|
final double extraHeight;
|
||||||
|
|
||||||
|
/// 边框圆角大小
|
||||||
|
final double radius;
|
||||||
|
|
||||||
|
/// 主题颜色
|
||||||
|
final Color themeColor;
|
||||||
|
|
||||||
|
/// 更新事件
|
||||||
|
final VoidCallback onUpdate;
|
||||||
|
|
||||||
|
/// 可忽略更新
|
||||||
|
final bool enableIgnore;
|
||||||
|
|
||||||
|
/// 更新事件
|
||||||
|
final VoidCallback? onIgnore;
|
||||||
|
|
||||||
|
double progress;
|
||||||
|
|
||||||
|
/// 进度条的背景颜色
|
||||||
|
final Color progressBackgroundColor;
|
||||||
|
|
||||||
|
/// 更新事件
|
||||||
|
final VoidCallback? onClose;
|
||||||
|
|
||||||
|
/// 是否是强制更新
|
||||||
|
final bool isForce;
|
||||||
|
|
||||||
|
/// 更新按钮内容
|
||||||
|
final String updateButtonText;
|
||||||
|
|
||||||
|
/// 忽略按钮内容
|
||||||
|
final String ignoreButtonText;
|
||||||
|
|
||||||
|
UpdateWidget(
|
||||||
|
{Key? key,
|
||||||
|
this.width = 0.0,
|
||||||
|
required this.title,
|
||||||
|
required this.updateContent,
|
||||||
|
required this.onUpdate,
|
||||||
|
this.titleTextSize = 16.0,
|
||||||
|
this.contentTextSize = 14.0,
|
||||||
|
this.buttonTextSize = 14.0,
|
||||||
|
this.progress = -1.0,
|
||||||
|
this.progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||||
|
this.topImage,
|
||||||
|
this.extraHeight = 5.0,
|
||||||
|
this.radius = 4.0,
|
||||||
|
this.themeColor = Colors.red,
|
||||||
|
this.enableIgnore = false,
|
||||||
|
this.onIgnore,
|
||||||
|
this.isForce = false,
|
||||||
|
this.updateButtonText = '更新',
|
||||||
|
this.ignoreButtonText = '忽略此版本',
|
||||||
|
this.onClose})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final _UpdateWidgetState _state = _UpdateWidgetState();
|
||||||
|
|
||||||
|
void update(double progress) {
|
||||||
|
_state.update(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
_UpdateWidgetState createState() => _state;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdateWidgetState extends State<UpdateWidget> {
|
||||||
|
void update(double progress) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
widget.progress = progress;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final double dialogWidth = widget.width <= 0 ? getFitWidth(context) * 0.618 : widget.width;
|
||||||
|
return Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: SizedBox(
|
||||||
|
width: dialogWidth,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(
|
||||||
|
width: dialogWidth,
|
||||||
|
child: widget.topImage ?? Image.asset('assets/images/update_bg_app_top.png', fit: BoxFit.fill),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: dialogWidth,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8),
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(widget.radius), bottomRight: Radius.circular(widget.radius)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(top: widget.extraHeight),
|
||||||
|
child: Text(widget.title, style: TextStyle(fontSize: widget.titleTextSize, color: Colors.black)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: Text(widget.updateContent, style: TextStyle(fontSize: widget.contentTextSize, color: const Color(0xFF666666))),
|
||||||
|
),
|
||||||
|
Column(children: <Widget>[
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 1,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
textStyle: MaterialStateProperty.all(TextStyle(fontSize: widget.buttonTextSize)),
|
||||||
|
foregroundColor: MaterialStateProperty.all(Colors.white),
|
||||||
|
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5))),
|
||||||
|
elevation: MaterialStateProperty.all(0),
|
||||||
|
backgroundColor: MaterialStateProperty.all(widget.themeColor),
|
||||||
|
),
|
||||||
|
child: Text(widget.updateButtonText),
|
||||||
|
onPressed: widget.onUpdate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.enableIgnore && widget.onIgnore != null)
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 1,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
textStyle: MaterialStateProperty.all(TextStyle(fontSize: widget.buttonTextSize)),
|
||||||
|
foregroundColor: MaterialStateProperty.all(const Color(0xFF666666)),
|
||||||
|
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5))),
|
||||||
|
),
|
||||||
|
child: Text(widget.ignoreButtonText),
|
||||||
|
onPressed: widget.onIgnore,
|
||||||
|
))
|
||||||
|
else
|
||||||
|
const SizedBox()
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
if (!widget.isForce)
|
||||||
|
Column(children: <Widget>[
|
||||||
|
const SizedBox(width: 1.5, height: 50, child: DecoratedBox(decoration: BoxDecoration(color: Colors.white))),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 30,
|
||||||
|
constraints: const BoxConstraints(maxHeight: 30, maxWidth: 30),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: Image.asset(
|
||||||
|
'assets/images/update_ic_close.png',
|
||||||
|
),
|
||||||
|
onPressed: widget.onClose,
|
||||||
|
)
|
||||||
|
])
|
||||||
|
else
|
||||||
|
const SizedBox()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
double getFitWidth(BuildContext context) {
|
||||||
|
return min(getScreenHeight(context), getScreenWidth(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
double getScreenHeight(BuildContext context) {
|
||||||
|
return MediaQuery.of(context).size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getScreenWidth(BuildContext context) {
|
||||||
|
return MediaQuery.of(context).size.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,40 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../module/home/system_bean.dart';
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
|
static final SystemBean systemBean = SystemBean();
|
||||||
|
|
||||||
|
static bool isUpperVersion() {
|
||||||
|
try {
|
||||||
|
List<String>? version = Utils.systemBean.version?.split("\.");
|
||||||
|
|
||||||
|
String f = version?[0] ?? "2";
|
||||||
|
String s = version?[1] ?? "10";
|
||||||
|
String t = version?[2] ?? "0";
|
||||||
|
|
||||||
|
int first = int.parse(f);
|
||||||
|
int second = int.parse(s);
|
||||||
|
int third = int.parse(t);
|
||||||
|
|
||||||
|
/// 2.10.13 及以下版本
|
||||||
|
|
||||||
|
if (first > 2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (second > 10) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (third > 13) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void hideKeyBoard(BuildContext context) {
|
static void hideKeyBoard(BuildContext context) {
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
}
|
}
|
||||||
@@ -71,7 +104,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String formatMessageTime(int time) {
|
static String formatMessageTime(int time) {
|
||||||
if(time == 0){
|
if (time == 0) {
|
||||||
return "-";
|
return "-";
|
||||||
}
|
}
|
||||||
DateTime current = DateTime.now();
|
DateTime current = DateTime.now();
|
||||||
|
|||||||
231
pubspec.lock
@@ -21,7 +21,7 @@ packages:
|
|||||||
name: archive
|
name: archive
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.8"
|
version: "3.3.0"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -56,7 +56,7 @@ packages:
|
|||||||
name: build
|
name: build
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.3.0"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -70,7 +70,7 @@ packages:
|
|||||||
name: build_daemon
|
name: build_daemon
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.1.0"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -84,7 +84,7 @@ packages:
|
|||||||
name: build_runner
|
name: build_runner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.7"
|
version: "2.1.10"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -105,14 +105,14 @@ packages:
|
|||||||
name: built_value
|
name: built_value
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.4"
|
version: "8.2.0"
|
||||||
change_app_package_name:
|
change_app_package_name:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: change_app_package_name
|
name: change_app_package_name
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "1.0.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -196,7 +196,7 @@ packages:
|
|||||||
name: dio
|
name: dio
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.4"
|
version: "4.0.6"
|
||||||
dio_log:
|
dio_log:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -258,13 +258,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1"
|
version: "0.1.1"
|
||||||
flutter_highlight:
|
flutter_colorpicker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_highlight
|
name: flutter_colorpicker
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "1.0.3"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -279,13 +279,18 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_native_splash
|
name: flutter_native_splash
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "2.1.6"
|
||||||
flutter_riverpod:
|
flutter_riverpod:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -300,6 +305,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
flutter_speed_dial:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_speed_dial
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.5"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -316,7 +328,7 @@ packages:
|
|||||||
name: fluttertoast
|
name: fluttertoast
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.0.8"
|
version: "8.0.9"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -338,6 +350,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.2"
|
||||||
|
google_fonts:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: google_fonts
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -346,19 +365,26 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
highlight:
|
highlight:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: highlight
|
name: highlight
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.0"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.4"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http_multi_server
|
name: http_multi_server
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.2.0"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -372,7 +398,7 @@ packages:
|
|||||||
name: image
|
name: image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.3"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -415,6 +441,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.4"
|
version: "0.0.4"
|
||||||
|
lint:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lint
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.8.2"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -443,6 +476,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.11"
|
version: "0.12.11"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -471,6 +511,48 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.2"
|
||||||
|
package_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: package_info_plus
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
package_info_plus_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
package_info_plus_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
package_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
package_info_plus_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
package_info_plus_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -478,6 +560,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
|
path_provider:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.9"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.12"
|
||||||
|
path_provider_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_ios
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.8"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -485,6 +588,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.5"
|
||||||
|
path_provider_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -547,7 +657,7 @@ packages:
|
|||||||
name: pub_semver
|
name: pub_semver
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
pubspec_parse:
|
pubspec_parse:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -568,35 +678,35 @@ packages:
|
|||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.12"
|
version: "2.0.13"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.10"
|
version: "2.0.11"
|
||||||
shared_preferences_ios:
|
shared_preferences_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_ios
|
name: shared_preferences_ios
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.9"
|
version: "2.1.0"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_linux
|
name: shared_preferences_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.1.0"
|
||||||
shared_preferences_macos:
|
shared_preferences_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_macos
|
name: shared_preferences_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
shared_preferences_platform_interface:
|
shared_preferences_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -617,14 +727,14 @@ packages:
|
|||||||
name: shared_preferences_windows
|
name: shared_preferences_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.1.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf
|
name: shelf
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.0"
|
||||||
shelf_web_socket:
|
shelf_web_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -692,7 +802,7 @@ packages:
|
|||||||
name: synchronized
|
name: synchronized
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0+2"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -706,7 +816,7 @@ packages:
|
|||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.3"
|
version: "0.4.8"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -728,6 +838,62 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.4"
|
||||||
|
url_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.15"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.15"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.9"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -749,20 +915,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
widget_with_codeview:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: widget_with_codeview
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1+2"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.6"
|
version: "2.5.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0+1"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -778,5 +951,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.15.1 <3.0.0"
|
dart: ">=2.16.0 <3.0.0"
|
||||||
flutter: ">=2.5.0"
|
flutter: ">=2.10.1"
|
||||||
|
|||||||
34
pubspec.yaml
@@ -1,19 +1,21 @@
|
|||||||
name: qinglong_app
|
name: qinglong_app
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
version: 1.0.1+2
|
version: 1.0.8+10
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.15.1 <3.0.0"
|
sdk: ">=2.15.1 <3.0.0"
|
||||||
|
flutter: ">=2.10.1"
|
||||||
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
cupertino_icons: ^1.0.2
|
sdk: flutter
|
||||||
|
cupertino_icons: ^1.0.4
|
||||||
flutter_riverpod: ^1.0.3
|
flutter_riverpod: ^1.0.3
|
||||||
shared_preferences: ^2.0.12
|
shared_preferences: ^2.0.13
|
||||||
synchronized: ^3.0.0
|
synchronized: ^3.0.0
|
||||||
back_button_interceptor: ^5.0.2
|
back_button_interceptor: ^5.0.2
|
||||||
flutter_slidable: ^1.2.0
|
flutter_slidable: ^1.2.0
|
||||||
@@ -23,25 +25,35 @@ dependencies:
|
|||||||
logger: ^1.1.0
|
logger: ^1.1.0
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
get_it: ^7.2.0
|
get_it: ^7.2.0
|
||||||
flutter_highlight: ^0.7.0
|
highlight: ^0.7.0
|
||||||
drag_and_drop_lists: ^0.3.2+2
|
drag_and_drop_lists: ^0.3.2+2
|
||||||
fluttertoast: ^8.0.8
|
fluttertoast: ^8.0.8
|
||||||
move_to_background: ^1.0.2
|
move_to_background: ^1.0.2
|
||||||
flip_card: ^0.6.0
|
flip_card: ^0.6.0
|
||||||
|
package_info_plus: ^1.4.0
|
||||||
|
url_launcher: ^6.0.20
|
||||||
|
flutter_colorpicker: ^1.0.3
|
||||||
|
widget_with_codeview: ^2.0.1+2
|
||||||
|
|
||||||
|
# flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
# flutter pub run change_app_package_name:main work.master.qinglongapp
|
||||||
|
# flutter pub run flutter_native_splash:create
|
||||||
|
# flutter pub run flutter_launcher_icons:main
|
||||||
|
# flutter pub run flutter_app_name
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_native_splash: ^1.3.3
|
flutter_native_splash: ^2.0.5
|
||||||
flutter_launcher_icons: ^0.9.2
|
flutter_launcher_icons: ^0.9.2
|
||||||
change_app_package_name: ^0.1.3
|
change_app_package_name: ^1.0.0
|
||||||
flutter_app_name: ^0.1.0
|
flutter_app_name: ^0.1.1
|
||||||
|
|
||||||
build_runner: ^2.0.0
|
build_runner: ^2.1.7
|
||||||
json_conversion: ^0.0.4
|
json_conversion: ^0.0.4
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^1.0.4
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
@@ -54,7 +66,7 @@ flutter_app_name:
|
|||||||
|
|
||||||
flutter_icons:
|
flutter_icons:
|
||||||
image_path_android: "assets/images/ql.png"
|
image_path_android: "assets/images/ql.png"
|
||||||
image_path_ios: "assets/images/ql.png"
|
image_path_ios: "assets/images/ql_white.png"
|
||||||
android: true # can specify file name here e.g. "ic_launcher"
|
android: true # can specify file name here e.g. "ic_launcher"
|
||||||
ios: true # can specify file name here e.g. "My-Launcher-Icon"
|
ios: true # can specify file name here e.g. "My-Launcher-Icon"
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,5 @@
|
|||||||
// tree, read text, and verify that the values of widget properties are correct.
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
String time = "Wed Jan 12 2022 20:33:39 GMT+0800 (中国标准时间)";
|
|
||||||
|
|
||||||
var result = DateTime.tryParse(time);
|
|
||||||
print(result);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,13 +31,15 @@
|
|||||||
|
|
||||||
<title>qinglong_app</title>
|
<title>qinglong_app</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<script src="splash/splash.js"></script>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
|
||||||
<link rel="stylesheet" type="text/css" href="splash/style.css">
|
<link rel="stylesheet" type="text/css" href="splash/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<picture id="splash">
|
<picture id="splash">
|
||||||
<source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light) or (prefers-color-scheme: no-preference)">
|
<source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light)">
|
||||||
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
|
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
|
||||||
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" />
|
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt=""/>
|
||||||
</picture>
|
</picture>
|
||||||
<!-- This script installs service_worker.js to provide PWA functionality to
|
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||||
application. For more information, see:
|
application. For more information, see:
|
||||||
|
|||||||
7
web/splash/splash.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
function removeSplashFromWeb() {
|
||||||
|
const elem = document.getElementById("splash");
|
||||||
|
if (elem) {
|
||||||
|
elem.remove();
|
||||||
|
}
|
||||||
|
document.body.style.background = "transparent";
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
body, html {
|
body {
|
||||||
margin:0;
|
margin:0;
|
||||||
height:100%;
|
height:100%;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
|||||||
17
windows/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
flutter/ephemeral/
|
||||||
|
|
||||||
|
# Visual Studio user-specific files.
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Visual Studio build-related files.
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
95
windows/CMakeLists.txt
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
project(qinglong_app LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(BINARY_NAME "qinglong_app")
|
||||||
|
|
||||||
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||||
|
|
||||||
|
# Configure build options.
|
||||||
|
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||||
|
if(IS_MULTICONFIG)
|
||||||
|
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
||||||
|
CACHE STRING "" FORCE)
|
||||||
|
else()
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||||
|
STRING "Flutter build mode" FORCE)
|
||||||
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||||
|
"Debug" "Profile" "Release")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
|
||||||
|
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
|
||||||
|
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||||
|
|
||||||
|
# Use Unicode for all projects.
|
||||||
|
add_definitions(-DUNICODE -D_UNICODE)
|
||||||
|
|
||||||
|
# Compilation settings that should be applied to most targets.
|
||||||
|
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||||
|
target_compile_features(${TARGET} PUBLIC cxx_std_17)
|
||||||
|
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
|
||||||
|
target_compile_options(${TARGET} PRIVATE /EHsc)
|
||||||
|
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
|
||||||
|
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
|
|
||||||
|
# Flutter library and tool build rules.
|
||||||
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
|
# Application build
|
||||||
|
add_subdirectory("runner")
|
||||||
|
|
||||||
|
# Generated plugin build rules, which manage building the plugins and adding
|
||||||
|
# them to the application.
|
||||||
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
|
# === Installation ===
|
||||||
|
# Support files are copied into place next to the executable, so that it can
|
||||||
|
# run in place. This is done instead of making a separate bundle (as on Linux)
|
||||||
|
# so that building and running from within Visual Studio will work.
|
||||||
|
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
||||||
|
# Make the "install" step default, as it's required to run.
|
||||||
|
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||||
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||||
|
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||||
|
|
||||||
|
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||||
|
# from a previous install.
|
||||||
|
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||||
|
|
||||||
|
# Install the AOT library on non-Debug builds only.
|
||||||
|
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
|
CONFIGURATIONS Profile;Release
|
||||||
|
COMPONENT Runtime)
|
||||||