5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Task Manager
2 | 一个简单的 Android 应用,为一些没有后台管理页功能的设备提供清理后台的能力,如`WearOS 2`
3 |
4 | 下载:https://bke.lanzoub.com/b02lstn5je
5 |
6 | 密码:30433
7 |
8 | 
9 |
10 | ### 使用
11 | `TaskManager`需要[`Shizuku`](https://github.com/RikkaApps/Shizuku)激活。对于安卓手表等小屏设备,我推荐使用[`Shizuku-wear`](https://github.com/java30433/Shizuku-wear)。激活方式不再赘述
12 |
13 | 在最近应用列表中,**单击**可以打开当前应用,**长按**可以查看应用信息,**从右往左滑**可以强行停止APP
14 |
15 | 由于方法的限制,最近应用列表中显示的并非打开过的APP,而是所有在后台中运行的APP进程,划卡强停后,所有于此APP相关的进程都会被杀死(类似于在设置中使用“强行停止”)。因此代码中预先隐藏了一部分重要的系统进程、Shizuku 和 TaskManager 本身。某些系统进程在强行停止后会自动重启,这是不可避免的,您可以长按后将其从任务列表中隐藏。
16 |
17 | ### 技术细节
18 | 使用了`Jetpack Compose`作为UI框架
19 |
20 | `lib-navigator`是我编写的 compose 导航库
21 |
22 | `lib-protostore`是我编写的基于`kotlinx-serialization-protobuf`实现的数据持久化库
23 |
24 | 所有代码均以 GPLv3 协议开源。
25 |
26 | ### 更新日志
27 | - 2024-08-29 v1.1.0
28 | - 添加了在列表中隐藏应用的功能
29 | - 在 Github 开源
30 | - 2024-08-28 v1.0.0
31 | - 第一个版本
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /signing.gradle.kts
3 | /release
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | apply("./signing.gradle.kts")
2 | plugins {
3 | alias(libs.plugins.android.application)
4 | alias(libs.plugins.jetbrains.kotlin.android)
5 | alias(libs.plugins.compose.compiler)
6 | alias(libs.plugins.kotlinx.serialization)
7 | }
8 |
9 | android {
10 | namespace = "wat.app.taskmanager"
11 | compileSdk = 34
12 |
13 | applicationVariants.configureEach {
14 | outputs.configureEach {
15 | (this as com.android.build.gradle.internal.api.BaseVariantOutputImpl).outputFileName =
16 | "TaskManager-${defaultConfig.versionName}.apk"
17 | }
18 | }
19 | signingConfigs {
20 | create("release") {
21 | @Suppress("UNCHECKED_CAST")
22 | fun ext(key: String) = rootProject.extra[key] as T
23 | storeFile = ext("storeFile")
24 | storePassword = ext("storePassword")
25 | keyAlias = ext("keyAlias")
26 | keyPassword = ext("keyPassword")
27 | }
28 | }
29 | defaultConfig {
30 | applicationId = "wat.app.taskmanager"
31 | minSdk = 24
32 | targetSdk = 35
33 | versionCode = 2
34 | versionName = "1.1.0"
35 |
36 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
37 | ndk {
38 | abiFilters.addAll(setOf("armeabi","x86","armeabi-v7a","x86_64","arm64-v8a"))
39 | }
40 | }
41 | buildFeatures {
42 | buildConfig = true
43 | compose = true
44 | }
45 | buildTypes {
46 | release {
47 | isMinifyEnabled = true
48 | proguardFiles(
49 | getDefaultProguardFile("proguard-android-optimize.txt"),
50 | "proguard-rules.pro"
51 | )
52 | signingConfig = signingConfigs["release"]
53 | }
54 | debug {
55 | isMinifyEnabled = false
56 | signingConfig = signingConfigs["release"]
57 | }
58 | }
59 | compileOptions {
60 | sourceCompatibility = JavaVersion.VERSION_1_8
61 | targetCompatibility = JavaVersion.VERSION_1_8
62 | }
63 | kotlinOptions {
64 | jvmTarget = "1.8"
65 | }
66 | composeOptions {
67 | kotlinCompilerExtensionVersion = "1.5.1"
68 | }
69 | }
70 |
71 | dependencies {
72 | implementation(project(":lib-components"))
73 | implementation(project(":lib-navigator"))
74 | implementation(project(":lib-protostore"))
75 | implementation(libs.shizuku.api)
76 | implementation(libs.shizuku.provider)
77 |
78 | implementation(libs.androidx.activity.compose)
79 | implementation(platform(libs.androidx.compose.bom))
80 | implementation(libs.androidx.compose.ui)
81 | implementation(libs.androidx.compose.foundation)
82 | implementation(libs.androidx.compose.ui.graphics)
83 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keepattributes SourceFile,LineNumberTable
2 | -ignorewarnings
3 | -optimizations !method/inlining/*
4 | -keepnames class wat.** { *; }
5 | -keepnames class bakuen.** { *; }
6 | -keep class * implements android.BinderInterface { *; }
--------------------------------------------------------------------------------
/app/src/androidTest/java/wat/app/taskmanager/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("wat.app.taskmanager", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/android/BinderInterface.java:
--------------------------------------------------------------------------------
1 | package android;
2 |
3 | /**
4 | * 这个类只是用来在 proguard 里作标记的,避免 android 包下面的 binder 被 r8 混淆掉
5 | */
6 | public interface BinderInterface {
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/android/app/IActivityManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.app;
18 |
19 | import android.BinderInterface;
20 | import android.os.Binder;
21 | import android.os.IBinder;
22 |
23 | import androidx.annotation.Keep;
24 |
25 | import java.util.List;
26 |
27 | public interface IActivityManager extends BinderInterface {
28 | List getRunningAppProcesses();
29 | void forceStopPackage(String packageName, int userId);
30 |
31 | abstract class Stub extends Binder implements IActivityManager {
32 | public static IActivityManager asInterface(IBinder obj) {
33 | throw new UnsupportedOperationException();
34 | }
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.ui.graphics.Color
8 | import bakuen.lib.navigator.NavHost
9 | import bakuen.wear.components.AutoSize
10 | import bakuen.wear.components.Surface
11 | import bakuen.wear.components.rememberViewModel
12 | import wat.app.taskmanager.screens.main.MainScreen
13 | import wat.app.taskmanager.screens.main.MainViewModel
14 | import wat.app.taskmanager.shizuku.ShizukuTools
15 | import wat.app.taskmanager.utils.Update
16 |
17 | lateinit var appContext: Context
18 | private set
19 | class MainActivity : ComponentActivity() {
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | appContext = applicationContext
24 | Update.check(this)
25 | ShizukuTools.requestPermission()
26 | setContent {
27 | AutoSize(designWidth = 192f) {
28 | NavHost(initScreen = { MainScreen(rememberViewModel { MainViewModel() }) }) {
29 | Surface(color = Color.Black, fillMaxSize = true) {
30 | it()
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
37 | override fun onPause() {
38 | super.onPause()
39 | finish()
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/prefs/Settings.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.prefs
2 |
3 | import bakuen.lib.protostore.ProtoStore
4 | import kotlinx.serialization.ExperimentalSerializationApi
5 | import kotlinx.serialization.Serializable
6 | import kotlinx.serialization.protobuf.ProtoNumber
7 |
8 | @Serializable
9 | data class Settings @OptIn(ExperimentalSerializationApi::class) constructor(
10 | @ProtoNumber(1)
11 | val hidePackages: Set = setOf()
12 | ) : ProtoStore
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/about/AboutScreen.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.about
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import bakuen.lib.navigator.Navigator
7 | import bakuen.wear.components.SurfaceButton
8 | import bakuen.wear.components.Text
9 | import wat.app.taskmanager.BuildConfig
10 | import wat.app.taskmanager.screens.hides.HideAppListScreen
11 | import wat.app.taskmanager.utils.ScreenColumn
12 |
13 | @Composable
14 | fun AboutScreen() {
15 | ScreenColumn(center = true) {
16 | Text(text = "任务管理器 TaskManager")
17 | Text(text = "版本:${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
18 | Text(text = "By java30433")
19 | Text(text = "交流Q群: 1002991206")
20 | Text(text = "以 GPLv3 协议开源")
21 | SurfaceButton(modifier = Modifier.clickable { Navigator.forward { HideAppListScreen() } }) {
22 | Text(text = "所有被隐藏应用的列表")
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/detail/AppDetailScreen.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.detail
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.focus.focusModifier
10 | import bakuen.lib.navigator.Navigator
11 | import bakuen.lib.protostore.setStore
12 | import bakuen.wear.components.Space
13 | import bakuen.wear.components.SurfaceButton
14 | import bakuen.wear.components.Text
15 | import wat.app.taskmanager.prefs.Settings
16 | import wat.app.taskmanager.utils.Const
17 | import wat.app.taskmanager.utils.ScreenColumn
18 | import wat.app.taskmanager.screens.main.AppInfo
19 |
20 | @Composable
21 | fun AppDetailScreen(info: AppInfo) {
22 | ScreenColumn {
23 | Space(size = Const.scrPaddingTop)
24 | Image(bitmap = info.iconBitmap, contentDescription = null)
25 | Text(text = info.label)
26 | Column(
27 | modifier = Modifier.fillMaxWidth()
28 | ) {
29 | Text(text = "应用包名:${info.packageName}")
30 | }
31 | SurfaceButton(modifier = Modifier.clickable {
32 | setStore { it.copy(hidePackages = it.hidePackages.plus(info.packageName)) }
33 | Navigator.navigateBack()
34 | }) {
35 | Text(text = "在列表中隐藏此应用")
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/hides/HideAppListScreen.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.hides
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import androidx.compose.ui.platform.LocalContext
10 | import androidx.compose.ui.unit.dp
11 | import bakuen.lib.navigator.SwipeToDismiss
12 | import bakuen.lib.protostore.getStore
13 | import bakuen.lib.protostore.rememberStore
14 | import bakuen.lib.protostore.setStore
15 | import bakuen.wear.components.Text
16 | import wat.app.taskmanager.prefs.Settings
17 | import wat.app.taskmanager.screens.main.AppCell
18 | import wat.app.taskmanager.screens.main.AppInfo
19 | import wat.app.taskmanager.utils.ScreenColumn
20 | import wat.app.taskmanager.utils.Title
21 |
22 | @Composable
23 | fun HideAppListScreen() {
24 | val pm = LocalContext.current.packageManager
25 | var settings by rememberStore()
26 | var state by remember { mutableStateOf(State()) }
27 | LaunchedEffect(settings.hidePackages.size) {
28 | state = state.copy(hideList = settings.hidePackages.map {
29 | val info = pm.getApplicationInfo(it, 0)
30 | AppInfo(
31 | packageName = it,
32 | icon = info.loadIcon(pm),
33 | label = info.loadLabel(pm).toString()
34 | )
35 | })
36 | }
37 | HideAppListScreenUI(state = state, dispatch = {
38 | when (it) {
39 | is Event.Remove -> settings =
40 | settings.copy(hidePackages = settings.hidePackages.minus(it.packageName))
41 | }
42 | })
43 | }
44 |
45 | private data class State(
46 | val hideList: List = listOf()
47 | )
48 |
49 | private sealed class Event {
50 | class Remove(val packageName: String) : Event()
51 | }
52 |
53 | @Composable
54 | private fun HideAppListScreenUI(state: State, dispatch: (Event) -> Unit) {
55 | ScreenColumn {
56 | Title {
57 | Text(text = "被隐藏的应用")
58 | }
59 | state.hideList.let {
60 | if (it.isEmpty()) Text(text = "没有被隐藏的应用!")
61 | else it.forEach {
62 | SwipeToDismiss(
63 | velocity = 10.dp,
64 | reverse = true,
65 | onDismiss = { dispatch(Event.Remove(it.packageName)) }
66 | ) {
67 | AppCell(info = it)
68 | }
69 | }
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/main/AppCell.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.main
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.combinedClickable
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Row
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.layout.size
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.asImageBitmap
16 | import androidx.compose.ui.unit.dp
17 | import androidx.core.graphics.drawable.toBitmap
18 | import bakuen.wear.components.Shapes
19 | import bakuen.wear.components.Text
20 | import bakuen.wear.components.Theme
21 |
22 |
23 | @OptIn(ExperimentalFoundationApi::class)
24 | @Composable
25 | fun AppCell(
26 | info: AppInfo,
27 | onLongClick: (() -> Unit)? = null,
28 | onClick: () -> Unit = {}
29 | ) {
30 | Row(
31 | modifier = Modifier
32 | .fillMaxWidth()
33 | .background(color = Theme.color.surfaceContainer, shape = Shapes.Cell)
34 | .combinedClickable(
35 | onClick = onClick,
36 | onLongClick = onLongClick
37 | )
38 | .padding(horizontal = 12.dp, vertical = 6.dp),
39 | verticalAlignment = Alignment.CenterVertically,
40 | horizontalArrangement = Arrangement.spacedBy(4.dp)
41 | ) {
42 | Image(
43 | modifier = Modifier.size(28.dp),
44 | bitmap = info.iconBitmap,
45 | contentDescription = null
46 | )
47 | Text(text = info.label)
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/main/AppInfo.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.main
2 |
3 | import android.graphics.drawable.Drawable
4 | import androidx.compose.ui.graphics.asImageBitmap
5 | import androidx.core.graphics.drawable.toBitmap
6 |
7 | data class AppInfo(
8 | val packageName: String,
9 | val label: String,
10 | val icon: Drawable
11 | ) {
12 | val iconBitmap by lazy { icon.toBitmap().asImageBitmap() }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/main/InfoText.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.main
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.IntentFilter
7 | import android.content.res.Resources
8 | import android.os.BatteryManager
9 | import androidx.compose.foundation.clickable
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.DisposableEffect
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.produceState
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.platform.LocalContext
16 | import bakuen.wear.components.Text
17 | import kotlinx.coroutines.delay
18 | import java.text.SimpleDateFormat
19 | import java.util.Date
20 | import kotlin.math.roundToInt
21 |
22 |
23 | @Composable
24 | fun InfoText(onClick: ()->Unit = {}) {
25 | val time by produceState(initialValue = "") {
26 | do {
27 | value =
28 | SimpleDateFormat("HH:mm", Resources.getSystem().configuration.locales[0]).format(
29 | Date()
30 | )
31 | delay(1_000)
32 | } while (true)
33 | }
34 | val context = LocalContext.current
35 | var batteryReceiver: BroadcastReceiver? = null //TODO 不优雅
36 | val battery by produceState(initialValue = 0) {
37 | batteryReceiver = object : BroadcastReceiver() {
38 | override fun onReceive(p0: Context?, intent: Intent) {
39 | val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
40 | val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
41 | value = (level * 100 / scale.toFloat()).roundToInt()
42 | }
43 | }
44 | context.registerReceiver(batteryReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
45 | }
46 | DisposableEffect(Unit) {
47 | onDispose {
48 | context.unregisterReceiver(batteryReceiver)
49 | }
50 | }
51 | Text(modifier = Modifier.clickable(onClick = onClick), text = "$time $battery%")
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/main/MainEvent.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.main
2 |
3 | sealed class MainEvent {
4 | class Open(val packageName: String) : MainEvent()
5 | class Kill(val packageName: String) : MainEvent()
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/main/MainScreen.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.main
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.layout.wrapContentSize
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.LaunchedEffect
7 | import androidx.compose.runtime.key
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.unit.dp
10 | import bakuen.lib.navigator.Navigator
11 | import bakuen.lib.protostore.rememberStore
12 | import bakuen.wear.components.Space
13 | import bakuen.wear.components.SwipeToDismiss
14 | import bakuen.wear.components.Text
15 | import wat.app.taskmanager.prefs.Settings
16 | import wat.app.taskmanager.utils.ScreenColumn
17 | import wat.app.taskmanager.shizuku.ShizukuState
18 | import wat.app.taskmanager.screens.about.AboutScreen
19 | import wat.app.taskmanager.screens.detail.AppDetailScreen
20 | import wat.app.taskmanager.utils.Title
21 |
22 | @Composable
23 | fun MainScreen(model: MainViewModel) {
24 | LaunchedEffect(rememberStore().value.hidePackages.size) {
25 | model.updateAppList()
26 | }
27 | MainScreenUI(state = model.state, dispatch = model::dispatch)
28 | }
29 |
30 | @Composable
31 | private fun MainScreenUI(state: MainState, dispatch: (MainEvent)->Unit) {
32 | if (state.shizuku == ShizukuState.GRANTED) {
33 | ScreenColumn {
34 | Title {
35 | InfoText(onClick = {
36 | Navigator.forward { AboutScreen() }
37 | })
38 | }
39 | if (state.runningAppProgress.isEmpty()) {
40 | Text(text = "当前没有运行中的应用")
41 | } else state.runningAppProgress.forEach {
42 | key(it.packageName) {
43 | SwipeToDismiss(
44 | velocity = 16.dp,
45 | reverse = true,
46 | onDismiss = { dispatch(MainEvent.Kill(it.packageName)) }) {
47 | AppCell(
48 | info = it,
49 | onClick = { dispatch(MainEvent.Open(it.packageName)) },
50 | onLongClick = { Navigator.forward { AppDetailScreen(info = it) } }
51 | )
52 | }
53 | }
54 | }
55 | }
56 | } else {
57 | Text(
58 | modifier = Modifier
59 | .fillMaxSize()
60 | .wrapContentSize(),
61 | text = if (state.shizuku == ShizukuState.NOT_GRANTED) "请授予 Shizuku 权限!"
62 | else "Shizuku 服务不可用!"
63 | )
64 | }
65 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/main/MainState.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.main
2 |
3 | import wat.app.taskmanager.shizuku.ShizukuState
4 |
5 | data class MainState(
6 | val shizuku: ShizukuState,
7 | val runningAppProgress: List = listOf()
8 | )
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/screens/main/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.screens.main
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import bakuen.lib.protostore.getStore
7 | import bakuen.wear.components.SimpleViewModel
8 | import wat.app.taskmanager.utils.Const
9 | import wat.app.taskmanager.utils.PackageUtils.killApp
10 | import wat.app.taskmanager.utils.PackageUtils.launchApp
11 | import wat.app.taskmanager.shizuku.ShizukuAPI
12 | import wat.app.taskmanager.shizuku.ShizukuTools
13 | import wat.app.taskmanager.appContext
14 | import wat.app.taskmanager.prefs.Settings
15 |
16 | class MainViewModel : SimpleViewModel() {
17 |
18 | var state by mutableStateOf(MainState(shizuku = ShizukuTools.state))
19 | private set
20 |
21 | fun updateAppList() {
22 | ShizukuTools.requireBinder {
23 | val pm = appContext.packageManager
24 | state = state.copy(
25 | runningAppProgress = ShizukuAPI.ACTIVITY_MANAGER.runningAppProcesses
26 | .filter {
27 | !(Const.ignoreApps.contains(it.processName) ||
28 | killedApps.contains(it.processName) ||
29 | getStore().hidePackages.contains(it.processName))
30 | }
31 | .mapNotNull {
32 | runCatching {
33 | val info = pm.getApplicationInfo(it.processName, 0)
34 | AppInfo(
35 | packageName = it.processName,
36 | icon = info.loadIcon(pm),
37 | label = info.loadLabel(pm).toString()
38 | )
39 | }.getOrNull()
40 | }
41 | )
42 | }
43 | }
44 |
45 | private val killedApps = mutableSetOf()
46 | override fun dispatch(event: MainEvent) {
47 | when (event) {
48 | is MainEvent.Open -> appContext.launchApp(event.packageName)
49 | is MainEvent.Kill -> {
50 | appContext.killApp(event.packageName)
51 | killedApps.add(event.packageName)
52 | updateAppList()
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/shizuku/ShizukuAPI.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.shizuku
2 |
3 | import android.app.IActivityManager
4 | import android.content.Context
5 | import rikka.shizuku.ShizukuBinderWrapper
6 | import rikka.shizuku.SystemServiceHelper
7 |
8 | object ShizukuAPI {
9 | val ACTIVITY_MANAGER: IActivityManager by lazy {
10 | IActivityManager.Stub.asInterface(ShizukuBinderWrapper(
11 | SystemServiceHelper.getSystemService(Context.ACTIVITY_SERVICE)
12 | ))
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/shizuku/ShizukuState.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.shizuku
2 |
3 | enum class ShizukuState {
4 | GRANTED, NOT_GRANTED, UNAVAILABLE
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/shizuku/ShizukuTools.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.shizuku
2 |
3 | import android.content.pm.PackageManager
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import rikka.shizuku.Shizuku
8 |
9 | object ShizukuTools {
10 | private fun getShizukuState() = if (running) if (granted) ShizukuState.GRANTED else ShizukuState.NOT_GRANTED
11 | else ShizukuState.UNAVAILABLE
12 | var state by mutableStateOf(getShizukuState())
13 | private set
14 | init {
15 | Shizuku.addBinderReceivedListener {
16 | state = getShizukuState()
17 | }
18 | Shizuku.addBinderDeadListener {
19 | state = getShizukuState()
20 | }
21 | }
22 | val running get() = Shizuku.pingBinder()
23 |
24 | fun requireBinder(block: ()->Unit) {
25 | if (running && granted) block()
26 | else Shizuku.addBinderReceivedListener(block)
27 | }
28 |
29 | val granted get() = running && Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
30 | fun requestPermission(callback: () -> Unit = {}) {
31 | state = getShizukuState()
32 | if (!running) return
33 | if (!granted) {
34 | Shizuku.addRequestPermissionResultListener { _, _ ->
35 | callback()
36 | }
37 | Shizuku.requestPermission(30433)
38 | } else callback()
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/utils/Composables.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.utils
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.ColumnScope
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.wrapContentSize
9 | import androidx.compose.foundation.rememberScrollState
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.unit.dp
15 | import bakuen.lib.navigator.Navigator
16 | import bakuen.wear.components.Space
17 | import bakuen.wear.components.calculate
18 | import bakuen.wear.components.condition
19 | import bakuen.wear.components.wear.verticalRotaryScroll
20 | import wat.app.taskmanager.screens.about.AboutScreen
21 | import wat.app.taskmanager.screens.main.InfoText
22 |
23 | @Composable
24 | inline fun ScreenColumn(center: Boolean = false, bg: Color? = null, content: @Composable ColumnScope.()->Unit) {
25 | Column(
26 | modifier = Modifier
27 | .fillMaxSize()
28 | .calculate { if (bg!=null) Modifier.background(color = bg) else null }
29 | .padding(horizontal = Const.scrPaddingHor)
30 | .condition(center, Modifier.wrapContentSize(Alignment.Center))
31 | .verticalRotaryScroll(rememberScrollState()),
32 | horizontalAlignment = Alignment.CenterHorizontally,
33 | verticalArrangement = Const.colArrangement,
34 | content = content
35 | )
36 | }
37 |
38 | @Composable
39 | inline fun ColumnScope.Title(content: @Composable ColumnScope.()->Unit) {
40 | Space(size = Const.scrPaddingTop)
41 | content()
42 | Space(size = 8.dp)
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/utils/Const.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.utils
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.ColumnScope
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.wrapContentSize
10 | import androidx.compose.foundation.rememberScrollState
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.unit.dp
16 | import bakuen.wear.components.Space
17 | import bakuen.wear.components.calculate
18 | import bakuen.wear.components.condition
19 | import bakuen.wear.components.wear.verticalRotaryScroll
20 | import wat.app.taskmanager.BuildConfig
21 |
22 | object Const {
23 | val ignoreApps = setOf(
24 | BuildConfig.APPLICATION_ID,
25 | "moe.shizuku.privileged.api",
26 | "com.google.android.wearable.app",
27 | "com.google.android.gms",
28 | "com.android.connectivity.metrics",
29 | "com.android.se",
30 | "com.android.phone",
31 | "com.android.providers.calendar",
32 | "com.google.android.ext.services",
33 | "com.google.android.deskclock",
34 | "con.google.android.apps.handwriting.ime",
35 | "system"
36 | )
37 | val scrPaddingHor = 9.dp
38 | val scrPaddingTop = 10.dp
39 | val colArrangement = Arrangement.spacedBy(2.dp)
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/utils/PackageUtils.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.ComponentName
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.pm.PackageManager
8 | import android.text.TextUtils
9 | import wat.app.taskmanager.shizuku.ShizukuAPI
10 |
11 |
12 | object PackageUtils {
13 | fun Context.killApp(packageName: String) {
14 | ShizukuAPI.ACTIVITY_MANAGER.forceStopPackage(packageName, 0)
15 | }
16 |
17 | fun Context.launchApp(packageName: String) {
18 | getAppOpenIntentByPackageName(this, packageName)?.let {
19 | startActivity(it)
20 | }
21 | }
22 | @SuppressLint("QueryPermissionsNeeded")
23 | fun getAppOpenIntentByPackageName(context: Context, packageName: String): Intent? {
24 | var mainAct: String? = null
25 | val pkgMag = context.packageManager
26 | val intent = Intent(Intent.ACTION_MAIN)
27 | intent.addCategory(Intent.CATEGORY_LAUNCHER)
28 | intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_NEW_TASK)
29 | val list = pkgMag.queryIntentActivities(intent, PackageManager.GET_ACTIVITIES)
30 | for (i in list.indices) {
31 | val info = list[i]
32 | if (info.activityInfo.packageName == packageName) {
33 | mainAct = info.activityInfo.name
34 | break
35 | }
36 | }
37 | if (TextUtils.isEmpty(mainAct)) {
38 | return null
39 | }
40 | intent.setComponent(ComponentName(packageName, mainAct!!))
41 | return intent
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/wat/app/taskmanager/utils/Update.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager.utils
2 |
3 | import android.app.Activity
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.widget.Toast
7 | import wat.app.taskmanager.BuildConfig
8 | import java.io.BufferedReader
9 | import java.io.InputStreamReader
10 | import java.net.HttpURLConnection
11 | import java.net.URL
12 |
13 | object Update {
14 | fun check(activity: Activity) {
15 | Thread {
16 | try {
17 | val connection = URL("https://pastebin.com/raw/FjTceQ7F")
18 | .openConnection() as HttpURLConnection
19 | connection.requestMethod = "GET"
20 | if (connection.responseCode == HttpURLConnection.HTTP_OK) {
21 | val reader =
22 | BufferedReader(InputStreamReader(connection.inputStream))
23 | val response = StringBuilder()
24 | var line: String?
25 | while ((reader.readLine().also { line = it }) != null) {
26 | response.append(line)
27 | }
28 | reader.close()
29 | val info = response.toString().split("@")
30 | val newVersion = info[0].toInt()
31 | if (newVersion > BuildConfig.VERSION_CODE) {
32 | Handler(Looper.getMainLooper()).post {
33 | Toast.makeText(
34 | activity,
35 | info[1].replace("\\n", "\n"),
36 | Toast.LENGTH_LONG
37 | ).show()
38 | }
39 | }
40 | }
41 | connection.disconnect()
42 | } catch (e: Exception) {
43 | e.printStackTrace()
44 | }
45 | }.start()
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3DDCB7
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/theme.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/test/java/wat/app/taskmanager/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package wat.app.taskmanager
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.android.library) apply false
5 | alias(libs.plugins.jetbrains.kotlin.android) apply false
6 | alias(libs.plugins.compose.compiler) apply false
7 | alias(libs.plugins.kotlinx.serialization) apply false
8 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.5.1"
3 | shizuku-api = "13.1.5"
4 | kotlin = "2.0.0"
5 | activityCompose = "1.9.1"
6 | composeBom = "2024.08.00"
7 | m3color = "2024.5"
8 | wearComposeFoundation = "1.4.0-rc01"
9 | kotlinxCoroutines = "1.9.0-RC.2"
10 | kotlinxSerialization = "1.6.3"
11 |
12 | [libraries]
13 | m3color = { module = "com.github.Kyant0:m3color", version.ref = "m3color" }
14 | androidx-wear-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wearComposeFoundation" }
15 | androidx-compose-animation = { group = "androidx.compose.animation", name = "animation" }
16 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
17 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
18 | androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
19 | androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version = "1.7.0-rc01" }
20 | androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
21 | shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku-api" }
22 | shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku-api" }
23 | kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinxSerialization" }
24 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
25 |
26 | [plugins]
27 | android-library = { id = "com.android.library", version.ref = "agp" }
28 | android-application = { id = "com.android.application", version.ref = "agp" }
29 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
30 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
31 | kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
32 |
33 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Aug 25 21:39:07 CST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/lib-components/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lib-components/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.jetbrains.kotlin.android)
4 | alias(libs.plugins.compose.compiler)
5 | }
6 |
7 | android {
8 | namespace = "bakuen.wear.components"
9 | compileSdk = 34
10 |
11 | defaultConfig {
12 | minSdk = 21
13 | consumerProguardFiles("consumer-rules.pro")
14 | }
15 |
16 | buildTypes {
17 | release {
18 | isMinifyEnabled = false
19 | proguardFiles(
20 | getDefaultProguardFile("proguard-android-optimize.txt"),
21 | "proguard-rules.pro"
22 | )
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility = JavaVersion.VERSION_1_8
27 | targetCompatibility = JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = "1.8"
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation(libs.m3color)
36 | implementation(platform(libs.androidx.compose.bom))
37 | implementation(libs.androidx.compose.ui)
38 | implementation(libs.androidx.compose.foundation)
39 | implementation(libs.androidx.compose.animation)
40 |
41 | implementation(libs.androidx.wear.compose.foundation)
42 | }
--------------------------------------------------------------------------------
/lib-components/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/lib-components/consumer-rules.pro
--------------------------------------------------------------------------------
/lib-components/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/lib-components/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/AutoSize.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import androidx.compose.ui.platform.LocalContext
6 | import androidx.compose.ui.platform.LocalDensity
7 | import androidx.compose.ui.unit.Density
8 |
9 | @Composable
10 | fun AutoSize(designWidth: Float, content: @Composable ()->Unit) {
11 | val fontScale = LocalDensity.current.fontScale
12 | val displayMetrics = LocalContext.current.resources.displayMetrics
13 | val widthPixels = displayMetrics.widthPixels
14 | CompositionLocalProvider(
15 | LocalDensity provides Density(
16 | density = widthPixels / designWidth,
17 | fontScale = fontScale
18 | ),
19 | content = content
20 | )
21 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Background.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.BoxScope
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 |
12 | @Composable
13 | inline fun Surface(
14 | color: Color = Theme.color.surface,
15 | fillMaxSize: Boolean = false,
16 | contentAlignment: Alignment = Alignment.TopStart,
17 | content: @Composable BoxScope.() -> Unit
18 | ) {
19 | Box(
20 | modifier = Modifier
21 | .background(color = color)
22 | .cleanClick()
23 | .condition(fillMaxSize, Modifier.fillMaxSize())
24 | , content = content, contentAlignment = contentAlignment
25 | )
26 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Button.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.Arrangement
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.PaddingValues
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.RowScope
11 | import androidx.compose.foundation.layout.fillMaxWidth
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.shape.CutCornerShape
14 | import androidx.compose.foundation.shape.RoundedCornerShape
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.runtime.CompositionLocalProvider
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.text.TextStyle
21 | import androidx.compose.ui.unit.dp
22 |
23 | @Composable
24 | fun SurfaceButton(
25 | modifier: Modifier = Modifier,
26 | content: @Composable RowScope.() -> Unit,
27 | ) {
28 | FilledButton(
29 | modifier = modifier,
30 | content = content,
31 | color = Theme.color.surfaceContainer,
32 | textColor = Theme.color.onSurface
33 | )
34 | }
35 |
36 | @Composable
37 | fun TextButton(
38 | modifier: Modifier = Modifier,
39 | padding: PaddingValues = PaddingValues(vertical = 4.dp, horizontal = 6.dp),
40 | onClick: () -> Unit,
41 | content: @Composable RowScope.() -> Unit,
42 | ) {
43 | Box(
44 | modifier = Modifier
45 | .clickable(onClick = onClick)
46 | .padding(padding)
47 | ) {
48 | CompositionLocalProvider(LocalTextColor provides Theme.color.primary) {
49 | Row(
50 | modifier = modifier,
51 | verticalAlignment = Alignment.CenterVertically,
52 | horizontalArrangement = Arrangement.Center,
53 | content = content
54 | )
55 | }
56 | }
57 | }
58 |
59 | @Composable
60 | private fun FilledButton(
61 | modifier: Modifier = Modifier,
62 | content: @Composable RowScope.() -> Unit,
63 | color: Color,
64 | textColor: Color,
65 | ) {
66 | Box(
67 | modifier = Modifier
68 | .fillMaxWidth()
69 | .background(color = color, shape = RoundedCornerShape(100))
70 | .padding(vertical = 12.dp, horizontal = 16.dp),
71 | contentAlignment = Alignment.Center
72 | ) {
73 | CompositionLocalProvider(LocalTextColor provides textColor) {
74 | Row(
75 | modifier = modifier,
76 | verticalAlignment = Alignment.CenterVertically,
77 | horizontalArrangement = Arrangement.Center,
78 | content = content
79 | )
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Density.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import android.content.res.Resources
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.platform.LocalDensity
6 |
7 | @Composable
8 | fun Float.pxToDp() = with(LocalDensity.current) {
9 | this@pxToDp.toDp()
10 | }
11 |
12 | object DensityData {
13 | val screenHeight = Resources.getSystem().displayMetrics.heightPixels
14 | val screenWidth = Resources.getSystem().displayMetrics.widthPixels
15 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Icon.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.ColorFilter
12 | import androidx.compose.ui.graphics.painter.Painter
13 | import androidx.compose.ui.res.painterResource
14 | import androidx.compose.ui.unit.Dp
15 |
16 | @Composable
17 | fun Icon(modifier: Modifier, @DrawableRes res: Int, color: Color = LocalTextColor.current) {
18 | Image(
19 | modifier = modifier,
20 | painter = painterResource(id = res),
21 | contentDescription = null,
22 | colorFilter = ColorFilter.tint(color)
23 | )
24 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Modifier.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.platform.LocalContext
10 | import androidx.compose.ui.platform.LocalDensity
11 | import kotlin.math.sqrt
12 |
13 | fun Modifier.calculate(block: ()->Modifier?) =
14 | block()?.let { then(it) } ?: then(this)
15 |
16 | fun Modifier.condition(condition: Boolean, ifTrue: Modifier, ifFalse: Modifier = this) =
17 | if (condition) then(ifTrue) else then(ifFalse)
18 |
19 | @Composable
20 | fun Modifier.cleanClick(onClick: (() -> Unit) = {}) = then(
21 | Modifier.clickable(
22 | interactionSource = remember { MutableInteractionSource() },
23 | indication = null,
24 | onClick = onClick
25 | )
26 | )
27 |
28 | @Composable
29 | fun Modifier.inscribedBoxSize() =
30 | then(Modifier.size((LocalContext.current.resources.displayMetrics.widthPixels / sqrt(2f)).pxToDp()))
31 |
32 | internal fun Modifier.nullableClick(onClick: (() -> Unit)?): Modifier {
33 | return then(Modifier.clickable(onClick = onClick ?: return this))
34 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Shapes.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 |
5 | object Shapes {
6 | val Cell = RoundedCornerShape(100)
7 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/SimpleViewModel.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import kotlinx.coroutines.MainScope
6 |
7 | @Composable
8 | fun > rememberViewModel(factory: ()->VM) = remember(factory)
9 |
10 | abstract class SimpleViewModel {
11 | val scope = MainScope()
12 | abstract fun dispatch(event: T)
13 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Space.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.foundation.layout.ColumnScope
4 | import androidx.compose.foundation.layout.RowScope
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.width
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.Dp
11 |
12 | @Composable
13 | fun ColumnScope.Space(size: Dp) {
14 | Spacer(modifier = Modifier.height(size))
15 | }
16 |
17 | @Composable
18 | fun RowScope.Space(size: Dp) {
19 | Spacer(modifier = Modifier.width(size))
20 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/SwipeToDismiss.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import android.content.res.Resources
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.animation.rememberSplineBasedDecay
6 | import androidx.compose.foundation.ExperimentalFoundationApi
7 | import androidx.compose.foundation.gestures.AnchoredDraggableState
8 | import androidx.compose.foundation.gestures.DraggableAnchors
9 | import androidx.compose.foundation.gestures.Orientation
10 | import androidx.compose.foundation.gestures.anchoredDraggable
11 | import androidx.compose.foundation.layout.Box
12 | import androidx.compose.foundation.layout.BoxScope
13 | import androidx.compose.foundation.layout.offset
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.platform.LocalDensity
18 | import androidx.compose.ui.unit.Dp
19 | import androidx.compose.ui.unit.IntOffset
20 |
21 | private enum class DragAnchors {
22 | Normal,
23 | Dismiss,
24 | }
25 |
26 | @OptIn(ExperimentalFoundationApi::class)
27 | @Composable
28 | fun SwipeToDismiss(
29 | modifier: Modifier = Modifier,
30 | velocity: Dp,
31 | onDismiss: ()->Unit,
32 | reverse: Boolean = false,
33 | content: @Composable BoxScope.()->Unit
34 | ) {
35 | val reverseSymbol = if (reverse) -1 else 1
36 | val maxOffset = reverseSymbol * Resources.getSystem().displayMetrics.widthPixels.toFloat()
37 | val density = LocalDensity.current
38 | val decay = rememberSplineBasedDecay()
39 | val state = remember {
40 | AnchoredDraggableState(
41 | initialValue = DragAnchors.Normal,
42 | positionalThreshold = { totalDistance ->
43 | totalDistance * 0.5f
44 | },
45 | velocityThreshold = {
46 | with(density) {
47 | velocity.toPx()
48 | }
49 | },
50 | snapAnimationSpec = tween(),
51 | decayAnimationSpec = decay,
52 | anchors = DraggableAnchors {
53 | DragAnchors.Normal at 0f
54 | DragAnchors.Dismiss at maxOffset
55 | }
56 | )
57 | }
58 | Box(
59 | modifier = modifier
60 | .offset {
61 | val x = state.requireOffset()
62 | if (x == maxOffset) onDismiss()
63 | IntOffset(x = x.toInt(), y = 0)
64 | }
65 | .anchoredDraggable(
66 | state = state,
67 | orientation = Orientation.Horizontal,
68 | ),
69 | content = content
70 | )
71 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Text.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.foundation.text.BasicText
4 | import androidx.compose.foundation.text.input.TextFieldLineLimits
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.staticCompositionLocalOf
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.text.TextStyle
10 |
11 | val LocalTextColor = staticCompositionLocalOf { Theme.color.onSurface }
12 |
13 | @Composable
14 | fun Text(
15 | modifier: Modifier = Modifier,
16 | text: String,
17 | style: TextStyle = Theme.typo.body,
18 | color: Color = LocalTextColor.current,
19 | lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
20 | ) {
21 | val minLines =
22 | if (lineLimits is TextFieldLineLimits.MultiLine) lineLimits.minHeightInLines else 1
23 | val maxLines =
24 | if (lineLimits is TextFieldLineLimits.MultiLine) lineLimits.maxHeightInLines else 1
25 | BasicText(
26 | modifier = modifier, text = text, style = style.copy(
27 | color = color
28 | ), minLines = minLines, maxLines = maxLines,
29 | )
30 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/TextField.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.text.BasicTextField
7 | import androidx.compose.foundation.text.KeyboardOptions
8 | import androidx.compose.foundation.text.input.TextFieldLineLimits
9 | import androidx.compose.foundation.text.input.TextFieldState
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.text.TextStyle
13 |
14 | @Composable
15 | fun TextField(
16 | modifier: Modifier = Modifier,
17 | state: TextFieldState,
18 | textStyle: TextStyle = Theme.typo.body,
19 | keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
20 | lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
21 | hint: String = ""
22 | ) {
23 | BasicTextField(
24 | modifier = modifier,
25 | state = state,
26 | textStyle = textStyle,
27 | keyboardOptions = keyboardOptions,
28 | lineLimits = lineLimits,
29 | decorator = { innerInputField ->
30 | if (state.text.isEmpty()) Text(
31 | text = hint,
32 | color = Theme.color.onSurfaceVariant,
33 | style = textStyle,
34 | lineLimits = TextFieldLineLimits.SingleLine
35 | )
36 | innerInputField()
37 | }
38 | )
39 | }
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/Theme.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.toArgb
5 | import androidx.compose.ui.text.TextStyle
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 | import com.kyant.m3color.hct.Hct
9 | import com.kyant.m3color.scheme.SchemeTonalSpot
10 |
11 | object Theme {
12 | var color = ColorScheme.fromPrimary(keyColor = Color(0xFF_769CDF))
13 | val typo = Typo(
14 | body = TextStyle.Default.copy(
15 | color = color.onSurface,
16 | fontWeight = FontWeight.Normal,
17 | fontSize = 11.sp
18 | )
19 | )
20 |
21 | }
22 |
23 | data class ColorScheme(
24 | val primary: Color,
25 | val onPrimary: Color,
26 | val primaryContainer: Color,
27 | val onPrimaryContainer: Color,
28 | val surface: Color,
29 | val onSurface: Color,
30 | val surfaceContainer: Color,
31 | val onSurfaceVariant: Color,
32 | val outline: Color,
33 | ) {
34 | companion object {
35 | fun fromPrimary(
36 | keyColor: Color,
37 | isDark: Boolean = true,
38 | pureDark: Boolean = true,
39 | contrastLevel: Double = 0.0
40 | ): ColorScheme {
41 | val hct = Hct.fromInt(keyColor.toArgb())
42 | val scheme = SchemeTonalSpot(hct, isDark, contrastLevel)
43 | val surface =
44 | if (pureDark) if (isDark) 0xFF000000.toInt() else 0xFFFFFFFF.toInt() else scheme.surface
45 | return ColorScheme(
46 | primary = Color(scheme.primary),
47 | onPrimary = Color(scheme.onPrimary),
48 | primaryContainer = Color(scheme.primaryContainer),
49 | onPrimaryContainer = Color(scheme.onPrimaryContainer),
50 | surface = Color(surface),
51 | surfaceContainer = Color(scheme.surfaceContainer),
52 | onSurface = Color(scheme.onSurface),
53 | onSurfaceVariant = Color(scheme.outline),
54 | outline = Color(scheme.outline)
55 | )
56 | }
57 | }
58 | }
59 |
60 | data class Typo(
61 | val body: TextStyle,
62 | val bodySmall: TextStyle = body.copy(
63 | fontSize = body.fontSize.times(0.9f)
64 | ),
65 | val title: TextStyle = body.copy(
66 | fontWeight = FontWeight.Bold
67 | ),
68 | val titleSmall: TextStyle = title.copy(
69 | fontSize = title.fontSize.times(0.9f)
70 | )
71 | )
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/ExperimentalMaterial3Api.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package bakuen.wear.components.material3
18 |
19 | @RequiresOptIn(
20 | "This material API is experimental and is likely to change or to be removed in" + " the future."
21 | )
22 | @Retention(AnnotationRetention.BINARY)
23 | annotation class ExperimentalMaterial3Api
24 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/ProgressIndicator.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package bakuen.wear.components.material3
18 |
19 | import androidx.compose.animation.core.LinearEasing
20 | import androidx.compose.animation.core.Spring
21 | import androidx.compose.animation.core.SpringSpec
22 | import androidx.compose.animation.core.animateFloat
23 | import androidx.compose.animation.core.infiniteRepeatable
24 | import androidx.compose.animation.core.keyframes
25 | import androidx.compose.animation.core.rememberInfiniteTransition
26 | import androidx.compose.animation.core.tween
27 | import androidx.compose.foundation.Canvas
28 | import androidx.compose.foundation.layout.size
29 | import androidx.compose.foundation.progressSemantics
30 | import bakuen.wear.components.material3.ProgressIndicatorDefaults.drawStopIndicator
31 | import bakuen.wear.components.material3.internal.IncreaseVerticalSemanticsBounds
32 | import androidx.compose.material3.tokens.CircularProgressIndicatorTokens
33 | import bakuen.wear.components.material3.tokens.LinearProgressIndicatorTokens
34 | import bakuen.wear.components.material3.tokens.MotionTokens
35 | import bakuen.wear.components.material3.tokens.ProgressIndicatorTokens
36 | import androidx.compose.runtime.Composable
37 | import androidx.compose.ui.Modifier
38 | import androidx.compose.ui.geometry.Offset
39 | import androidx.compose.ui.geometry.Size
40 | import androidx.compose.ui.graphics.Color
41 | import androidx.compose.ui.graphics.StrokeCap
42 | import androidx.compose.ui.graphics.drawscope.DrawScope
43 | import androidx.compose.ui.graphics.drawscope.Stroke
44 | import androidx.compose.ui.graphics.drawscope.rotate
45 | import androidx.compose.ui.platform.LocalDensity
46 | import androidx.compose.ui.semantics.ProgressBarRangeInfo
47 | import androidx.compose.ui.semantics.progressBarRangeInfo
48 | import androidx.compose.ui.semantics.semantics
49 | import androidx.compose.ui.unit.Dp
50 | import androidx.compose.ui.unit.LayoutDirection
51 | import androidx.compose.ui.unit.dp
52 | import kotlin.math.PI
53 | import kotlin.math.abs
54 | import kotlin.math.max
55 | import kotlin.math.min
56 |
57 | /**
58 | * Determinate Material Design linear progress indicator.
60 | *
61 | * Progress indicators express an unspecified wait time or display the duration of a process.
62 | *
63 | * 
65 | *
66 | * By default there is no animation between [progress] values. You can use
67 | * [ProgressIndicatorDefaults.ProgressAnimationSpec] as the default recommended [AnimationSpec] when
68 | * animating progress, such as in the following example:
69 | *
70 | * @sample androidx.compose.material3.samples.LinearProgressIndicatorSample
71 | * @param progress the progress of this progress indicator, where 0.0 represents no progress and 1.0
72 | * represents full progress. Values outside of this range are coerced into the range.
73 | * @param modifier the [Modifier] to be applied to this progress indicator
74 | * @param color color of this progress indicator
75 | * @param trackColor color of the track behind the indicator, visible when the progress has not
76 | * reached the area of the overall indicator yet
77 | * @param strokeCap stroke cap to use for the ends of this progress indicator
78 | */
79 | @Deprecated(
80 | message =
81 | "Use the overload that takes `gapSize` and `drawStopIndicator`, see " +
82 | "`LegacyLinearProgressIndicatorSample` on how to restore the previous behavior",
83 | replaceWith =
84 | ReplaceWith(
85 | "LinearProgressIndicator(progress, modifier, color, trackColor, strokeCap, " +
86 | "gapSize, drawStopIndicator)"
87 | ),
88 | level = DeprecationLevel.HIDDEN
89 | )
90 | @OptIn(ExperimentalMaterial3Api::class)
91 | @Composable
92 | fun LinearProgressIndicator(
93 | progress: () -> Float,
94 | modifier: Modifier = Modifier,
95 | color: Color = ProgressIndicatorDefaults.linearColor,
96 | trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
97 | strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
98 | ) {
99 | LinearProgressIndicator(
100 | progress,
101 | modifier,
102 | color,
103 | trackColor,
104 | strokeCap,
105 | gapSize = ProgressIndicatorDefaults.LinearIndicatorTrackGapSize
106 | )
107 | }
108 |
109 | /**
110 | * Determinate Material Design linear progress indicator.
112 | *
113 | * Progress indicators express an unspecified wait time or display the duration of a process.
114 | *
115 | * 
117 | *
118 | * By default there is no animation between [progress] values. You can use
119 | * [ProgressIndicatorDefaults.ProgressAnimationSpec] as the default recommended [AnimationSpec] when
120 | * animating progress, such as in the following example:
121 | *
122 | * @sample androidx.compose.material3.samples.LinearProgressIndicatorSample
123 | * @param progress the progress of this progress indicator, where 0.0 represents no progress and 1.0
124 | * represents full progress. Values outside of this range are coerced into the range.
125 | * @param modifier the [Modifier] to be applied to this progress indicator
126 | * @param color color of this progress indicator
127 | * @param trackColor color of the track behind the indicator, visible when the progress has not
128 | * reached the area of the overall indicator yet
129 | * @param strokeCap stroke cap to use for the ends of this progress indicator
130 | * @param gapSize size of the gap between the progress indicator and the track
131 | * @param drawStopIndicator lambda that will be called to draw the stop indicator
132 | */
133 | @OptIn(ExperimentalMaterial3Api::class)
134 | @Composable
135 | fun LinearProgressIndicator(
136 | progress: () -> Float,
137 | modifier: Modifier = Modifier,
138 | color: Color = ProgressIndicatorDefaults.linearColor,
139 | trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
140 | strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
141 | gapSize: Dp = ProgressIndicatorDefaults.LinearIndicatorTrackGapSize,
142 | drawStopIndicator: DrawScope.() -> Unit = {
143 | drawStopIndicator(
144 | drawScope = this,
145 | stopSize = ProgressIndicatorDefaults.LinearTrackStopIndicatorSize,
146 | color = color,
147 | strokeCap = strokeCap
148 | )
149 | },
150 | ) {
151 | val coercedProgress = { progress().coerceIn(0f, 1f) }
152 | Canvas(
153 | modifier
154 | .then(IncreaseVerticalSemanticsBounds)
155 | .semantics(mergeDescendants = true) {
156 | progressBarRangeInfo = ProgressBarRangeInfo(coercedProgress(), 0f..1f)
157 | }
158 | .size(LinearIndicatorWidth, LinearIndicatorHeight)
159 | ) {
160 | val strokeWidth = size.height
161 | val adjustedGapSize =
162 | if (strokeCap == StrokeCap.Butt || size.height > size.width) {
163 | gapSize
164 | } else {
165 | gapSize + strokeWidth.toDp()
166 | }
167 | val gapSizeFraction = adjustedGapSize / size.width.toDp()
168 | val currentCoercedProgress = coercedProgress()
169 |
170 | // track
171 | val trackStartFraction =
172 | currentCoercedProgress + min(currentCoercedProgress, gapSizeFraction)
173 | if (trackStartFraction <= 1f) {
174 | drawLinearIndicator(trackStartFraction, 1f, trackColor, strokeWidth, strokeCap)
175 | }
176 | // indicator
177 | drawLinearIndicator(0f, currentCoercedProgress, color, strokeWidth, strokeCap)
178 | // stop
179 | drawStopIndicator(this)
180 | }
181 | }
182 |
183 | /**
184 | * Indeterminate Material Design linear progress indicator.
186 | *
187 | * Progress indicators express an unspecified wait time or display the duration of a process.
188 | *
189 | * 
191 | *
192 | * @sample androidx.compose.material3.samples.IndeterminateLinearProgressIndicatorSample
193 | * @param modifier the [Modifier] to be applied to this progress indicator
194 | * @param color color of this progress indicator
195 | * @param trackColor color of the track behind the indicator, visible when the progress has not
196 | * reached the area of the overall indicator yet
197 | * @param strokeCap stroke cap to use for the ends of this progress indicator
198 | */
199 | @Deprecated(
200 | message =
201 | "Use the overload that takes `gapSize`, see `" +
202 | "LegacyIndeterminateLinearProgressIndicatorSample` on how to restore the previous behavior",
203 | replaceWith =
204 | ReplaceWith("LinearProgressIndicator(modifier, color, trackColor, strokeCap, gapSize)"),
205 | level = DeprecationLevel.HIDDEN
206 | )
207 | @OptIn(ExperimentalMaterial3Api::class)
208 | @Composable
209 | fun LinearProgressIndicator(
210 | modifier: Modifier = Modifier,
211 | color: Color = ProgressIndicatorDefaults.linearColor,
212 | trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
213 | strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
214 | ) {
215 | LinearProgressIndicator(
216 | modifier,
217 | color,
218 | trackColor,
219 | strokeCap,
220 | gapSize = ProgressIndicatorDefaults.LinearIndicatorTrackGapSize,
221 | )
222 | }
223 |
224 | /**
225 | * Indeterminate Material Design linear progress indicator.
227 | *
228 | * Progress indicators express an unspecified wait time or display the duration of a process.
229 | *
230 | * 
232 | *
233 | * @sample androidx.compose.material3.samples.IndeterminateLinearProgressIndicatorSample
234 | * @param modifier the [Modifier] to be applied to this progress indicator
235 | * @param color color of this progress indicator
236 | * @param trackColor color of the track behind the indicator, visible when the progress has not
237 | * reached the area of the overall indicator yet
238 | * @param strokeCap stroke cap to use for the ends of this progress indicator
239 | * @param gapSize size of the gap between the progress indicator and the track
240 | */
241 | @OptIn(ExperimentalMaterial3Api::class)
242 | @Composable
243 | fun LinearProgressIndicator(
244 | modifier: Modifier = Modifier,
245 | color: Color = ProgressIndicatorDefaults.linearColor,
246 | trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
247 | strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
248 | gapSize: Dp = ProgressIndicatorDefaults.LinearIndicatorTrackGapSize,
249 | ) {
250 | val infiniteTransition = rememberInfiniteTransition()
251 | val firstLineHead =
252 | infiniteTransition.animateFloat(
253 | initialValue = 0f,
254 | targetValue = 1f,
255 | animationSpec = linearIndeterminateFirstLineHeadAnimationSpec
256 | )
257 | val firstLineTail =
258 | infiniteTransition.animateFloat(
259 | initialValue = 0f,
260 | targetValue = 1f,
261 | animationSpec = linearIndeterminateFirstLineTailAnimationSpec
262 | )
263 | val secondLineHead =
264 | infiniteTransition.animateFloat(
265 | initialValue = 0f,
266 | targetValue = 1f,
267 | animationSpec = linearIndeterminateSecondLineHeadAnimationSpec
268 | )
269 | val secondLineTail =
270 | infiniteTransition.animateFloat(
271 | initialValue = 0f,
272 | targetValue = 1f,
273 | animationSpec = linearIndeterminateSecondLineTailAnimationSpec
274 | )
275 | Canvas(
276 | modifier
277 | .then(IncreaseVerticalSemanticsBounds)
278 | .progressSemantics()
279 | .size(LinearIndicatorWidth, LinearIndicatorHeight)
280 | ) {
281 | val strokeWidth = size.height
282 | val adjustedGapSize =
283 | if (strokeCap == StrokeCap.Butt || size.height > size.width) {
284 | gapSize
285 | } else {
286 | gapSize + strokeWidth.toDp()
287 | }
288 | val gapSizeFraction = adjustedGapSize / size.width.toDp()
289 |
290 | // Track before line 1
291 | if (firstLineHead.value < 1f - gapSizeFraction) {
292 | val start = if (firstLineHead.value > 0) firstLineHead.value + gapSizeFraction else 0f
293 | drawLinearIndicator(start, 1f, trackColor, strokeWidth, strokeCap)
294 | }
295 |
296 | // Line 1
297 | if (firstLineHead.value - firstLineTail.value > 0) {
298 | drawLinearIndicator(
299 | firstLineHead.value,
300 | firstLineTail.value,
301 | color,
302 | strokeWidth,
303 | strokeCap,
304 | )
305 | }
306 |
307 | // Track between line 1 and line 2
308 | if (firstLineTail.value > gapSizeFraction) {
309 | val start = if (secondLineHead.value > 0) secondLineHead.value + gapSizeFraction else 0f
310 | val end = if (firstLineTail.value < 1f) firstLineTail.value - gapSizeFraction else 1f
311 | drawLinearIndicator(start, end, trackColor, strokeWidth, strokeCap)
312 | }
313 |
314 | // Line 2
315 | if (secondLineHead.value - secondLineTail.value > 0) {
316 | drawLinearIndicator(
317 | secondLineHead.value,
318 | secondLineTail.value,
319 | color,
320 | strokeWidth,
321 | strokeCap,
322 | )
323 | }
324 |
325 | // Track after line 2
326 | if (secondLineTail.value > gapSizeFraction) {
327 | val end = if (secondLineTail.value < 1) secondLineTail.value - gapSizeFraction else 1f
328 | drawLinearIndicator(0f, end, trackColor, strokeWidth, strokeCap)
329 | }
330 | }
331 | }
332 |
333 | @Deprecated(
334 | message = "Use the overload that takes `progress` as a lambda",
335 | replaceWith =
336 | ReplaceWith(
337 | "LinearProgressIndicator(\n" +
338 | "progress = { progress },\n" +
339 | "modifier = modifier,\n" +
340 | "color = color,\n" +
341 | "trackColor = trackColor,\n" +
342 | "strokeCap = strokeCap,\n" +
343 | ")"
344 | )
345 | )
346 | @Composable
347 | fun LinearProgressIndicator(
348 | progress: Float,
349 | modifier: Modifier = Modifier,
350 | color: Color = ProgressIndicatorDefaults.linearColor,
351 | trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
352 | strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
353 | ) =
354 | LinearProgressIndicator(
355 | progress = { progress },
356 | modifier = modifier,
357 | color = color,
358 | trackColor = trackColor,
359 | strokeCap = strokeCap,
360 | )
361 |
362 | @Suppress("DEPRECATION")
363 | @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
364 | @Composable
365 | fun LinearProgressIndicator(
366 | progress: Float,
367 | modifier: Modifier = Modifier,
368 | color: Color = ProgressIndicatorDefaults.linearColor,
369 | trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
370 | ) =
371 | LinearProgressIndicator(
372 | progress,
373 | modifier,
374 | color,
375 | trackColor,
376 | strokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
377 | )
378 |
379 | @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
380 | @Composable
381 | fun LinearProgressIndicator(
382 | modifier: Modifier = Modifier,
383 | color: Color = ProgressIndicatorDefaults.linearColor,
384 | trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
385 | ) =
386 | LinearProgressIndicator(
387 | modifier,
388 | color,
389 | trackColor,
390 | strokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
391 | )
392 |
393 | private fun DrawScope.drawLinearIndicator(
394 | startFraction: Float,
395 | endFraction: Float,
396 | color: Color,
397 | strokeWidth: Float,
398 | strokeCap: StrokeCap,
399 | ) {
400 | val width = size.width
401 | val height = size.height
402 | // Start drawing from the vertical center of the stroke
403 | val yOffset = height / 2
404 |
405 | val isLtr = layoutDirection == LayoutDirection.Ltr
406 | val barStart = (if (isLtr) startFraction else 1f - endFraction) * width
407 | val barEnd = (if (isLtr) endFraction else 1f - startFraction) * width
408 |
409 | // if there isn't enough space to draw the stroke caps, fall back to StrokeCap.Butt
410 | if (strokeCap == StrokeCap.Butt || height > width) {
411 | // Progress line
412 | drawLine(color, Offset(barStart, yOffset), Offset(barEnd, yOffset), strokeWidth)
413 | } else {
414 | // need to adjust barStart and barEnd for the stroke caps
415 | val strokeCapOffset = strokeWidth / 2
416 | val coerceRange = strokeCapOffset..(width - strokeCapOffset)
417 | val adjustedBarStart = barStart.coerceIn(coerceRange)
418 | val adjustedBarEnd = barEnd.coerceIn(coerceRange)
419 |
420 | if (abs(endFraction - startFraction) > 0) {
421 | // Progress line
422 | drawLine(
423 | color,
424 | Offset(adjustedBarStart, yOffset),
425 | Offset(adjustedBarEnd, yOffset),
426 | strokeWidth,
427 | strokeCap,
428 | )
429 | }
430 | }
431 | }
432 |
433 | /**
434 | * Determinate Material Design circular progress indicator.
436 | *
437 | * Progress indicators express an unspecified wait time or display the duration of a process.
438 | *
439 | * 
441 | *
442 | * By default there is no animation between [progress] values. You can use
443 | * [ProgressIndicatorDefaults.ProgressAnimationSpec] as the default recommended [AnimationSpec] when
444 | * animating progress, such as in the following example:
445 | *
446 | * @sample androidx.compose.material3.samples.CircularProgressIndicatorSample
447 | * @param progress the progress of this progress indicator, where 0.0 represents no progress and 1.0
448 | * represents full progress. Values outside of this range are coerced into the range.
449 | * @param modifier the [Modifier] to be applied to this progress indicator
450 | * @param color color of this progress indicator
451 | * @param strokeWidth stroke width of this progress indicator
452 | * @param trackColor color of the track behind the indicator, visible when the progress has not
453 | * reached the area of the overall indicator yet
454 | * @param strokeCap stroke cap to use for the ends of this progress indicator
455 | */
456 | @Deprecated(
457 | message =
458 | "Use the overload that takes `gapSize`, see " +
459 | "`LegacyCircularProgressIndicatorSample` on how to restore the previous behavior",
460 | replaceWith =
461 | ReplaceWith(
462 | "CircularProgressIndicator(progress, modifier, color, strokeWidth, trackColor, " +
463 | "strokeCap, gapSize)"
464 | ),
465 | level = DeprecationLevel.HIDDEN
466 | )
467 | @OptIn(ExperimentalMaterial3Api::class)
468 | @Composable
469 | fun CircularProgressIndicator(
470 | progress: () -> Float,
471 | modifier: Modifier = Modifier,
472 | color: Color = ProgressIndicatorDefaults.circularColor,
473 | strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,
474 | trackColor: Color = ProgressIndicatorDefaults.circularDeterminateTrackColor,
475 | strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularDeterminateStrokeCap,
476 | ) {
477 | CircularProgressIndicator(
478 | progress,
479 | modifier,
480 | color,
481 | strokeWidth,
482 | trackColor,
483 | strokeCap,
484 | gapSize = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize
485 | )
486 | }
487 |
488 | /**
489 | * Determinate Material Design circular progress indicator.
491 | *
492 | * Progress indicators express an unspecified wait time or display the duration of a process.
493 | *
494 | * 
496 | *
497 | * By default there is no animation between [progress] values. You can use
498 | * [ProgressIndicatorDefaults.ProgressAnimationSpec] as the default recommended [AnimationSpec] when
499 | * animating progress, such as in the following example:
500 | *
501 | * @sample androidx.compose.material3.samples.CircularProgressIndicatorSample
502 | * @param progress the progress of this progress indicator, where 0.0 represents no progress and 1.0
503 | * represents full progress. Values outside of this range are coerced into the range.
504 | * @param modifier the [Modifier] to be applied to this progress indicator
505 | * @param color color of this progress indicator
506 | * @param strokeWidth stroke width of this progress indicator
507 | * @param trackColor color of the track behind the indicator, visible when the progress has not
508 | * reached the area of the overall indicator yet
509 | * @param strokeCap stroke cap to use for the ends of this progress indicator
510 | * @param gapSize size of the gap between the progress indicator and the track
511 | */
512 | @OptIn(ExperimentalMaterial3Api::class)
513 | @Composable
514 | fun CircularProgressIndicator(
515 | progress: () -> Float,
516 | modifier: Modifier = Modifier,
517 | color: Color = ProgressIndicatorDefaults.circularColor,
518 | strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,
519 | trackColor: Color = ProgressIndicatorDefaults.circularDeterminateTrackColor,
520 | strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularDeterminateStrokeCap,
521 | gapSize: Dp = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize,
522 | ) {
523 | val coercedProgress = { progress().coerceIn(0f, 1f) }
524 | val stroke = with(LocalDensity.current) { Stroke(width = strokeWidth.toPx(), cap = strokeCap) }
525 | Canvas(
526 | modifier
527 | .semantics(mergeDescendants = true) {
528 | progressBarRangeInfo = ProgressBarRangeInfo(coercedProgress(), 0f..1f)
529 | }
530 | .size(CircularIndicatorDiameter)
531 | ) {
532 | // Start at 12 o'clock
533 | val startAngle = 270f
534 | val sweep = coercedProgress() * 360f
535 | val adjustedGapSize =
536 | if (strokeCap == StrokeCap.Butt || size.height > size.width) {
537 | gapSize
538 | } else {
539 | gapSize + strokeWidth
540 | }
541 | val gapSizeSweep = (adjustedGapSize.value / (PI * size.width.toDp().value).toFloat()) * 360f
542 |
543 | drawCircularIndicator(
544 | startAngle + sweep + min(sweep, gapSizeSweep),
545 | 360f - sweep - min(sweep, gapSizeSweep) * 2,
546 | trackColor,
547 | stroke
548 | )
549 | drawDeterminateCircularIndicator(startAngle, sweep, color, stroke)
550 | }
551 | }
552 |
553 | /**
554 | * Indeterminate Material Design circular progress indicator.
556 | *
557 | * Progress indicators express an unspecified wait time or display the duration of a process.
558 | *
559 | * 
561 | *
562 | * @sample androidx.compose.material3.samples.IndeterminateCircularProgressIndicatorSample
563 | * @param modifier the [Modifier] to be applied to this progress indicator
564 | * @param color color of this progress indicator
565 | * @param strokeWidth stroke width of this progress indicator
566 | * @param trackColor color of the track behind the indicator, visible when the progress has not
567 | * reached the area of the overall indicator yet
568 | * @param strokeCap stroke cap to use for the ends of this progress indicator
569 | */
570 | @Deprecated(
571 | message = "Use the overload that takes `gapSize`",
572 | replaceWith =
573 | ReplaceWith(
574 | "CircularProgressIndicator(modifier, color, strokeWidth, trackColor, strokeCap, " +
575 | "gapSize)"
576 | ),
577 | level = DeprecationLevel.HIDDEN
578 | )
579 | @OptIn(ExperimentalMaterial3Api::class)
580 | @Composable
581 | fun CircularProgressIndicator(
582 | modifier: Modifier = Modifier,
583 | color: Color = ProgressIndicatorDefaults.circularColor,
584 | strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,
585 | trackColor: Color = ProgressIndicatorDefaults.circularIndeterminateTrackColor,
586 | strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularIndeterminateStrokeCap,
587 | ) =
588 | CircularProgressIndicator(
589 | modifier = modifier,
590 | color = color,
591 | strokeWidth = strokeWidth,
592 | trackColor = trackColor,
593 | strokeCap = strokeCap,
594 | gapSize = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize
595 | )
596 |
597 | /**
598 | * Indeterminate Material Design circular progress indicator.
600 | *
601 | * Progress indicators express an unspecified wait time or display the duration of a process.
602 | *
603 | * 
605 | *
606 | * @sample androidx.compose.material3.samples.IndeterminateCircularProgressIndicatorSample
607 | * @param modifier the [Modifier] to be applied to this progress indicator
608 | * @param color color of this progress indicator
609 | * @param strokeWidth stroke width of this progress indicator
610 | * @param trackColor color of the track behind the indicator, visible when the progress has not
611 | * reached the area of the overall indicator yet
612 | * @param strokeCap stroke cap to use for the ends of this progress indicator
613 | * @param gapSize size of the gap between the progress indicator and the track
614 | */
615 | @OptIn(ExperimentalMaterial3Api::class)
616 | @Composable
617 | fun CircularProgressIndicator(
618 | modifier: Modifier = Modifier,
619 | color: Color = ProgressIndicatorDefaults.circularColor,
620 | strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,
621 | trackColor: Color = ProgressIndicatorDefaults.circularIndeterminateTrackColor,
622 | strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularIndeterminateStrokeCap,
623 | gapSize: Dp = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize
624 | ) {
625 | val stroke = with(LocalDensity.current) { Stroke(width = strokeWidth.toPx(), cap = strokeCap) }
626 |
627 | val infiniteTransition = rememberInfiniteTransition()
628 | // A global rotation that does a 360 degrees rotation in 6 seconds.
629 | val globalRotation =
630 | infiniteTransition.animateFloat(
631 | initialValue = 0f,
632 | targetValue = CircularGlobalRotationDegreesTarget,
633 | animationSpec = circularIndeterminateGlobalRotationAnimationSpec
634 | )
635 |
636 | // An additional rotation that moves by 90 degrees in 500ms and then rest for 1 second.
637 | val additionalRotation =
638 | infiniteTransition.animateFloat(
639 | initialValue = 0f,
640 | targetValue = CircularAdditionalRotationDegreesTarget,
641 | animationSpec = circularIndeterminateRotationAnimationSpec
642 | )
643 |
644 | // Indicator progress animation that will be changing the progress up and down as the indicator
645 | // rotates.
646 | val progressAnimation =
647 | infiniteTransition.animateFloat(
648 | initialValue = CircularIndeterminateMinProgress,
649 | targetValue = CircularIndeterminateMaxProgress,
650 | animationSpec = circularIndeterminateProgressAnimationSpec
651 | )
652 |
653 | Canvas(modifier.progressSemantics().size(CircularIndicatorDiameter)) {
654 | val sweep = progressAnimation.value * 360f
655 | val adjustedGapSize =
656 | if (strokeCap == StrokeCap.Butt || size.height > size.width) {
657 | gapSize
658 | } else {
659 | gapSize + strokeWidth
660 | }
661 | val gapSizeSweep = (adjustedGapSize.value / (PI * size.width.toDp().value).toFloat()) * 360f
662 |
663 | rotate(globalRotation.value + additionalRotation.value) {
664 | drawCircularIndicator(
665 | sweep + min(sweep, gapSizeSweep),
666 | 360f - sweep - min(sweep, gapSizeSweep) * 2,
667 | trackColor,
668 | stroke
669 | )
670 | drawDeterminateCircularIndicator(startAngle = 0f, sweep, color, stroke)
671 | }
672 | }
673 | }
674 |
675 | @Suppress("DEPRECATION")
676 | @Deprecated(
677 | message = "Use the overload that takes `progress` as a lambda",
678 | replaceWith =
679 | ReplaceWith(
680 | "CircularProgressIndicator(\n" +
681 | "progress = { progress },\n" +
682 | "modifier = modifier,\n" +
683 | "color = color,\n" +
684 | "strokeWidth = strokeWidth,\n" +
685 | "trackColor = trackColor,\n" +
686 | "strokeCap = strokeCap,\n" +
687 | ")"
688 | )
689 | )
690 | @Composable
691 | fun CircularProgressIndicator(
692 | progress: Float,
693 | modifier: Modifier = Modifier,
694 | color: Color = ProgressIndicatorDefaults.circularColor,
695 | strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,
696 | trackColor: Color = ProgressIndicatorDefaults.circularTrackColor,
697 | strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularDeterminateStrokeCap,
698 | ) =
699 | CircularProgressIndicator(
700 | progress = { progress },
701 | modifier = modifier,
702 | color = color,
703 | strokeWidth = strokeWidth,
704 | trackColor = trackColor,
705 | strokeCap = strokeCap,
706 | )
707 |
708 | @Suppress("DEPRECATION")
709 | @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
710 | @Composable
711 | fun CircularProgressIndicator(
712 | progress: Float,
713 | modifier: Modifier = Modifier,
714 | color: Color = ProgressIndicatorDefaults.circularColor,
715 | strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth
716 | ) =
717 | CircularProgressIndicator(
718 | progress,
719 | modifier,
720 | color,
721 | strokeWidth,
722 | trackColor = ProgressIndicatorDefaults.circularTrackColor,
723 | strokeCap = ProgressIndicatorDefaults.CircularDeterminateStrokeCap,
724 | )
725 |
726 | @Suppress("DEPRECATION")
727 | @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
728 | @Composable
729 | fun CircularProgressIndicator(
730 | modifier: Modifier = Modifier,
731 | color: Color = ProgressIndicatorDefaults.circularColor,
732 | strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth
733 | ) =
734 | CircularProgressIndicator(
735 | modifier,
736 | color,
737 | strokeWidth,
738 | trackColor = ProgressIndicatorDefaults.circularTrackColor,
739 | strokeCap = ProgressIndicatorDefaults.CircularIndeterminateStrokeCap,
740 | )
741 |
742 | private fun DrawScope.drawCircularIndicator(
743 | startAngle: Float,
744 | sweep: Float,
745 | color: Color,
746 | stroke: Stroke
747 | ) {
748 | // To draw this circle we need a rect with edges that line up with the midpoint of the stroke.
749 | // To do this we need to remove half the stroke width from the total diameter for both sides.
750 | val diameterOffset = stroke.width / 2
751 | val arcDimen = size.width - 2 * diameterOffset
752 | drawArc(
753 | color = color,
754 | startAngle = startAngle,
755 | sweepAngle = sweep,
756 | useCenter = false,
757 | topLeft = Offset(diameterOffset, diameterOffset),
758 | size = Size(arcDimen, arcDimen),
759 | style = stroke
760 | )
761 | }
762 |
763 | private fun DrawScope.drawCircularIndicatorTrack(color: Color, stroke: Stroke) =
764 | drawCircularIndicator(0f, 360f, color, stroke)
765 |
766 | private fun DrawScope.drawDeterminateCircularIndicator(
767 | startAngle: Float,
768 | sweep: Float,
769 | color: Color,
770 | stroke: Stroke
771 | ) = drawCircularIndicator(startAngle, sweep, color, stroke)
772 |
773 | private fun DrawScope.drawIndeterminateCircularIndicator(
774 | startAngle: Float,
775 | strokeWidth: Dp,
776 | sweep: Float,
777 | color: Color,
778 | stroke: Stroke
779 | ) {
780 | val strokeCapOffset =
781 | if (stroke.cap == StrokeCap.Butt) {
782 | 0f
783 | } else {
784 | // Length of arc is angle * radius
785 | // Angle (radians) is length / radius
786 | // The length should be the same as the stroke width for calculating the min angle
787 | (180.0 / PI).toFloat() * (strokeWidth / (CircularIndicatorDiameter / 2)) / 2f
788 | }
789 |
790 | // Adding a stroke cap draws half the stroke width behind the start point, so we want to
791 | // move it forward by that amount so the arc visually appears in the correct place
792 | val adjustedStartAngle = startAngle + strokeCapOffset
793 |
794 | // When the start and end angles are in the same place, we still want to draw a small sweep, so
795 | // the stroke caps get added on both ends and we draw the correct minimum length arc
796 | val adjustedSweep = max(sweep, 0.1f)
797 |
798 | drawCircularIndicator(adjustedStartAngle, adjustedSweep, color, stroke)
799 | }
800 |
801 | /**
802 | * Contains the default values used for [LinearProgressIndicator] and [CircularProgressIndicator].
803 | */
804 | object ProgressIndicatorDefaults {
805 | /** Default color for a linear progress indicator. */
806 | val linearColor: Color
807 | @Composable get() = ProgressIndicatorTokens.ActiveIndicatorColor
808 |
809 | /** Default color for a circular progress indicator. */
810 | val circularColor: Color
811 | @Composable get() = ProgressIndicatorTokens.ActiveIndicatorColor
812 |
813 | /** Default track color for a linear progress indicator. */
814 | val linearTrackColor: Color
815 | @Composable get() = ProgressIndicatorTokens.TrackColor
816 |
817 | /** Default track color for a circular progress indicator. */
818 | @Deprecated(
819 | "Renamed to circularDeterminateTrackColor or circularIndeterminateTrackColor",
820 | ReplaceWith("ProgressIndicatorDefaults.circularIndeterminateTrackColor"),
821 | DeprecationLevel.WARNING
822 | )
823 | val circularTrackColor: Color
824 | @Composable get() = Color.Transparent
825 |
826 | /** Default track color for a circular determinate progress indicator. */
827 | val circularDeterminateTrackColor: Color
828 | @Composable get() = ProgressIndicatorTokens.TrackColor
829 |
830 | /** Default track color for a circular indeterminate progress indicator. */
831 | val circularIndeterminateTrackColor: Color
832 | @Composable get() = Color.Transparent
833 |
834 | /** Default stroke width for a circular progress indicator. */
835 | val CircularStrokeWidth: Dp = CircularProgressIndicatorTokens.TrackThickness
836 |
837 | /** Default stroke cap for a linear progress indicator. */
838 | val LinearStrokeCap: StrokeCap = StrokeCap.Round
839 |
840 | /** Default stroke cap for a determinate circular progress indicator. */
841 | val CircularDeterminateStrokeCap: StrokeCap = StrokeCap.Round
842 |
843 | /** Default stroke cap for an indeterminate circular progress indicator. */
844 | val CircularIndeterminateStrokeCap: StrokeCap = StrokeCap.Round
845 |
846 | /** Default track stop indicator size for a linear progress indicator. */
847 | @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
848 | @get:ExperimentalMaterial3Api
849 | @ExperimentalMaterial3Api
850 | val LinearTrackStopIndicatorSize: Dp = LinearProgressIndicatorTokens.StopSize
851 |
852 | /** Default indicator track gap size for a linear progress indicator. */
853 | @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
854 | @get:ExperimentalMaterial3Api
855 | @ExperimentalMaterial3Api
856 | val LinearIndicatorTrackGapSize: Dp = LinearProgressIndicatorTokens.TrackActiveSpace
857 |
858 | /** Default indicator track gap size for a circular progress indicator. */
859 | @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
860 | @get:ExperimentalMaterial3Api
861 | @ExperimentalMaterial3Api
862 | val CircularIndicatorTrackGapSize: Dp = CircularProgressIndicatorTokens.TrackActiveSpace
863 |
864 | /**
865 | * The default [AnimationSpec] that should be used when animating between progress in a
866 | * determinate progress indicator.
867 | */
868 | val ProgressAnimationSpec =
869 | SpringSpec(
870 | dampingRatio = Spring.DampingRatioNoBouncy,
871 | stiffness = Spring.StiffnessVeryLow,
872 | // The default threshold is 0.01, or 1% of the overall progress range, which is quite
873 | // large and noticeable. We purposefully choose a smaller threshold.
874 | visibilityThreshold = 1 / 1000f
875 | )
876 |
877 | /**
878 | * Draws the stop indicator at the end of the track.
879 | *
880 | * @param drawScope the [DrawScope]
881 | * @param stopSize size of this stop indicator, it cannot be bigger than the track's height
882 | * @param color color of this stop indicator
883 | * @param strokeCap stroke cap to use for the ends of this stop indicator
884 | */
885 | fun drawStopIndicator(
886 | drawScope: DrawScope,
887 | stopSize: Dp,
888 | color: Color,
889 | strokeCap: StrokeCap,
890 | ) {
891 | with(drawScope) {
892 | val adjustedStopSize =
893 | min(stopSize.toPx(), size.height) // Stop can't be bigger than track
894 | val stopOffset = (size.height - adjustedStopSize) / 2 // Offset from end
895 | if (strokeCap == StrokeCap.Round) {
896 | drawCircle(
897 | color = color,
898 | radius = adjustedStopSize / 2f,
899 | center =
900 | Offset(
901 | x = size.width - (adjustedStopSize / 2f) - stopOffset,
902 | y = size.height / 2f
903 | )
904 | )
905 | } else {
906 | drawRect(
907 | color = color,
908 | topLeft =
909 | Offset(
910 | x = size.width - adjustedStopSize - stopOffset,
911 | y = (size.height - adjustedStopSize) / 2f
912 | ),
913 | size = Size(width = adjustedStopSize, height = adjustedStopSize)
914 | )
915 | }
916 | }
917 | }
918 | }
919 |
920 | /** A global animation spec for indeterminate circular progress indicator. */
921 | internal val circularIndeterminateGlobalRotationAnimationSpec
922 | get() =
923 | infiniteRepeatable(
924 | animation = tween(CircularAnimationProgressDuration, easing = LinearEasing)
925 | )
926 |
927 | /**
928 | * An animation spec for indeterminate circular progress indicators that infinitely rotates a 360
929 | * degrees.
930 | */
931 | internal val circularIndeterminateRotationAnimationSpec
932 | get() =
933 | infiniteRepeatable(
934 | animation =
935 | keyframes {
936 | durationMillis = CircularAnimationProgressDuration // 6000ms
937 | 90f at
938 | CircularAnimationAdditionalRotationDuration using
939 | MotionTokens.EasingEmphasizedDecelerateCubicBezier // 300ms
940 | 90f at CircularAnimationAdditionalRotationDelay // hold till 1500ms
941 | 180f at
942 | CircularAnimationAdditionalRotationDuration +
943 | CircularAnimationAdditionalRotationDelay // 1800ms
944 | 180f at CircularAnimationAdditionalRotationDelay * 2 // hold till 3000ms
945 | 270f at
946 | CircularAnimationAdditionalRotationDuration +
947 | CircularAnimationAdditionalRotationDelay * 2 // 3300ms
948 | 270f at CircularAnimationAdditionalRotationDelay * 3 // hold till 4500ms
949 | 360f at
950 | CircularAnimationAdditionalRotationDuration +
951 | CircularAnimationAdditionalRotationDelay * 3 // 4800ms
952 | 360f at CircularAnimationProgressDuration // hold till 6000ms
953 | }
954 | )
955 |
956 | /** An animation spec for indeterminate circular progress indicators progress motion. */
957 | internal val circularIndeterminateProgressAnimationSpec
958 | get() =
959 | infiniteRepeatable(
960 | animation =
961 | keyframes {
962 | durationMillis = CircularAnimationProgressDuration // 6000ms
963 | CircularIndeterminateMaxProgress at
964 | CircularAnimationProgressDuration / 2 using
965 | CircularProgressEasing // 3000ms
966 | CircularIndeterminateMinProgress at CircularAnimationProgressDuration
967 | }
968 | )
969 |
970 | /** An animation spec for indeterminate linear progress indicator first line head position. */
971 | internal val linearIndeterminateFirstLineHeadAnimationSpec
972 | get() =
973 | infiniteRepeatable(
974 | animation =
975 | keyframes {
976 | durationMillis = LinearAnimationDuration
977 | 0f at FirstLineHeadDelay using LinearIndeterminateProgressEasing
978 | 1f at FirstLineHeadDuration + FirstLineHeadDelay
979 | }
980 | )
981 |
982 | /** An animation spec for indeterminate linear progress indicator first line tail position. */
983 | internal val linearIndeterminateFirstLineTailAnimationSpec
984 | get() =
985 | infiniteRepeatable(
986 | animation =
987 | keyframes {
988 | durationMillis = LinearAnimationDuration
989 | 0f at FirstLineTailDelay using LinearIndeterminateProgressEasing
990 | 1f at FirstLineTailDuration + FirstLineTailDelay
991 | }
992 | )
993 |
994 | /** An animation spec for indeterminate linear progress indicator second line head position. */
995 | internal val linearIndeterminateSecondLineHeadAnimationSpec
996 | get() =
997 | infiniteRepeatable(
998 | animation =
999 | keyframes {
1000 | durationMillis = LinearAnimationDuration
1001 | 0f at SecondLineHeadDelay using LinearIndeterminateProgressEasing
1002 | 1f at SecondLineHeadDuration + SecondLineHeadDelay
1003 | }
1004 | )
1005 |
1006 | /** An animation spec for indeterminate linear progress indicator second line tail position. */
1007 | internal val linearIndeterminateSecondLineTailAnimationSpec
1008 | get() =
1009 | infiniteRepeatable(
1010 | animation =
1011 | keyframes {
1012 | durationMillis = LinearAnimationDuration
1013 | 0f at SecondLineTailDelay using LinearIndeterminateProgressEasing
1014 | 1f at SecondLineTailDuration + SecondLineTailDelay
1015 | }
1016 | )
1017 | // LinearProgressIndicator Material specs
1018 |
1019 | // Width is given in the spec but not defined as a token.
1020 | /*@VisibleForTesting*/
1021 | internal val LinearIndicatorWidth = 240.dp
1022 |
1023 | /*@VisibleForTesting*/
1024 | internal val LinearIndicatorHeight = LinearProgressIndicatorTokens.Height
1025 |
1026 | // CircularProgressIndicator Material specs
1027 | // Diameter of the indicator circle
1028 | /*@VisibleForTesting*/
1029 | internal val CircularIndicatorDiameter = CircularProgressIndicatorTokens.Size
1030 |
1031 | // Indeterminate linear indicator transition specs
1032 |
1033 | // Total duration for one linear cycle
1034 | internal const val LinearAnimationDuration = 1750
1035 |
1036 | // Duration of the head and tail animations for both lines
1037 | internal const val FirstLineHeadDuration = 1000
1038 | internal const val FirstLineTailDuration = 1000
1039 | internal const val SecondLineHeadDuration = 850
1040 | internal const val SecondLineTailDuration = 850
1041 |
1042 | // Delay before the start of the head and tail animations for both lines
1043 | internal const val FirstLineHeadDelay = 0
1044 | internal const val FirstLineTailDelay = 250
1045 | internal const val SecondLineHeadDelay = 650
1046 | internal const val SecondLineTailDelay = 900
1047 |
1048 | internal val LinearIndeterminateProgressEasing = MotionTokens.EasingEmphasizedAccelerateCubicBezier
1049 |
1050 | // Indeterminate circular indicator transition specs
1051 |
1052 | // The indeterminate circular indicator easing constants for its motion
1053 | internal val CircularProgressEasing = MotionTokens.EasingStandardCubicBezier
1054 | internal const val CircularIndeterminateMinProgress = 0.1f
1055 | internal const val CircularIndeterminateMaxProgress = 0.87f
1056 |
1057 | internal const val CircularAnimationProgressDuration = 6000
1058 | internal const val CircularAnimationAdditionalRotationDelay = 1500
1059 | internal const val CircularAnimationAdditionalRotationDuration = 300
1060 | internal const val CircularAdditionalRotationDegreesTarget = 360f
1061 | internal const val CircularGlobalRotationDegreesTarget = 1080f
1062 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/internal/AccessibilityUtil.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package bakuen.wear.components.material3.internal
18 |
19 | import androidx.annotation.VisibleForTesting
20 | import androidx.compose.foundation.layout.padding
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.layout.layout
23 | import androidx.compose.ui.semantics.semantics
24 | import androidx.compose.ui.unit.Dp
25 | import androidx.compose.ui.unit.dp
26 | import androidx.compose.ui.unit.offset
27 |
28 | @VisibleForTesting internal val HorizontalSemanticsBoundsPadding: Dp = 10.dp
29 | @VisibleForTesting internal val VerticalSemanticsBoundsPadding: Dp = 10.dp
30 |
31 | /**
32 | * Increases the semantics bounds horizontally by [HorizontalSemanticsBoundsPadding] in order to
33 | * meet the TalkBack box minimum size while preserving the visual appearance.
34 | */
35 | internal val IncreaseHorizontalSemanticsBounds: Modifier =
36 | Modifier.layout { measurable, constraints ->
37 | val paddingPx = HorizontalSemanticsBoundsPadding.roundToPx()
38 | // We need to add horizontal padding to the semantics bounds in order to meet
39 | // screenreader green box minimum size, but we also want to
40 | // preserve a visual appearance and layout size below that minimum
41 | // in order to maintain backwards compatibility. This custom
42 | // layout effectively implements "negative padding".
43 | val newConstraint = constraints.offset(paddingPx * 2, 0)
44 | val placeable = measurable.measure(newConstraint)
45 |
46 | // But when actually placing the placeable, create the layout without additional
47 | // space. Place the placeable where it would've been without any extra padding.
48 | val height = placeable.height
49 | val width = placeable.width - paddingPx * 2
50 | layout(width, height) { placeable.place(-paddingPx, 0) }
51 | }
52 | .semantics(mergeDescendants = true) {}
53 | .padding(horizontal = HorizontalSemanticsBoundsPadding)
54 |
55 | /**
56 | * Increases the semantics bounds vertically by [VerticalSemanticsBoundsPadding] in order to meet
57 | * the TalkBack box minimum size while preserving the visual appearance.
58 | */
59 | internal val IncreaseVerticalSemanticsBounds: Modifier =
60 | Modifier.layout { measurable, constraints ->
61 | val paddingPx = VerticalSemanticsBoundsPadding.roundToPx()
62 | // We need to add vertical padding to the semantics bounds in order to meet
63 | // screenreader green box minimum size, but we also want to
64 | // preserve a visual appearance and layout size below that minimum
65 | // in order to maintain backwards compatibility. This custom
66 | // layout effectively implements "negative padding".
67 | val newConstraint = constraints.offset(0, paddingPx * 2)
68 | val placeable = measurable.measure(newConstraint)
69 |
70 | // But when actually placing the placeable, create the layout without additional
71 | // space. Place the placeable where it would've been without any extra padding.
72 | val height = placeable.height - paddingPx * 2
73 | val width = placeable.width
74 | layout(width, height) { placeable.place(0, -paddingPx) }
75 | }
76 | .semantics(mergeDescendants = true) {}
77 | .padding(vertical = VerticalSemanticsBoundsPadding)
78 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/tokens/CircularProgressIndicatorTokens.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | // VERSION: v0_7_0
17 | // GENERATED CODE - DO NOT MODIFY BY HAND
18 |
19 | package androidx.compose.material3.tokens
20 |
21 | import androidx.compose.ui.unit.dp
22 |
23 | internal object CircularProgressIndicatorTokens {
24 | val ActiveThickness = 4.0.dp
25 | val ActiveWaveAmplitude = 1.6.dp
26 | val ActiveWaveWavelength = 15.0.dp
27 | val Size = 40.0.dp
28 | val TrackActiveSpace = 4.0.dp
29 | val TrackThickness = 4.0.dp
30 | val WaveSize = 48.0.dp
31 | }
32 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/tokens/ColorSchemeKeyTokens.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components.material3.tokens
2 |
3 | import bakuen.wear.components.Theme
4 |
5 | object ColorSchemeKeyTokens {
6 | val Primary get() = Theme.color.primary
7 | val SecondaryContainer get() = Theme.color.onSurfaceVariant
8 | }
9 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/tokens/LinearProgressIndicatorTokens.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | // VERSION: v0_7_0
17 | // GENERATED CODE - DO NOT MODIFY BY HAND
18 |
19 | package bakuen.wear.components.material3.tokens
20 |
21 | import androidx.compose.ui.unit.dp
22 |
23 | internal object LinearProgressIndicatorTokens {
24 | val ActiveThickness = 4.0.dp
25 | val ActiveWaveAmplitude = 3.0.dp
26 | val ActiveWaveWavelength = 40.0.dp
27 | val Height = 4.0.dp
28 | val IndeterminateActiveWaveWavelength = 20.0.dp
29 | val StopSize = 4.0.dp
30 | val StopTrailingSpace = 0.0.dp
31 | val TrackActiveSpace = 4.0.dp
32 | val TrackThickness = 4.0.dp
33 | val WaveHeight = 10.0.dp
34 | }
35 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/tokens/MotionTokens.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | // VERSION: v0_103
17 | // GENERATED CODE - DO NOT MODIFY BY HAND
18 |
19 | package bakuen.wear.components.material3.tokens
20 |
21 | import androidx.compose.animation.core.CubicBezierEasing
22 |
23 | internal object MotionTokens {
24 | const val DurationExtraLong1 = 700.0
25 | const val DurationExtraLong2 = 800.0
26 | const val DurationExtraLong3 = 900.0
27 | const val DurationExtraLong4 = 1000.0
28 | const val DurationLong1 = 450.0
29 | const val DurationLong2 = 500.0
30 | const val DurationLong3 = 550.0
31 | const val DurationLong4 = 600.0
32 | const val DurationMedium1 = 250.0
33 | const val DurationMedium2 = 300.0
34 | const val DurationMedium3 = 350.0
35 | const val DurationMedium4 = 400.0
36 | const val DurationShort1 = 50.0
37 | const val DurationShort2 = 100.0
38 | const val DurationShort3 = 150.0
39 | const val DurationShort4 = 200.0
40 | val EasingEmphasizedCubicBezier = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f)
41 | val EasingEmphasizedAccelerateCubicBezier = CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f)
42 | val EasingEmphasizedDecelerateCubicBezier = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1.0f)
43 | val EasingLegacyCubicBezier = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)
44 | val EasingLegacyAccelerateCubicBezier = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)
45 | val EasingLegacyDecelerateCubicBezier = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)
46 | val EasingLinearCubicBezier = CubicBezierEasing(0.0f, 0.0f, 1.0f, 1.0f)
47 | val EasingStandardCubicBezier = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f)
48 | val EasingStandardAccelerateCubicBezier = CubicBezierEasing(0.3f, 0.0f, 1.0f, 1.0f)
49 | val EasingStandardDecelerateCubicBezier = CubicBezierEasing(0.0f, 0.0f, 0.0f, 1.0f)
50 | }
51 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/tokens/ProgressIndicatorTokens.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | // VERSION: v0_4_0
17 | // GENERATED CODE - DO NOT MODIFY BY HAND
18 |
19 | package bakuen.wear.components.material3.tokens
20 |
21 | internal object ProgressIndicatorTokens {
22 | val ActiveIndicatorColor = ColorSchemeKeyTokens.Primary
23 | val ActiveShape = ShapeKeyTokens.CornerFull
24 | val StopColor = ColorSchemeKeyTokens.Primary
25 | val StopShape = ShapeKeyTokens.CornerFull
26 | val TrackColor = ColorSchemeKeyTokens.SecondaryContainer
27 | val TrackShape = ShapeKeyTokens.CornerFull
28 | }
29 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/material3/tokens/ShapeKeyTokens.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components.material3.tokens
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 |
5 | object ShapeKeyTokens {
6 | val CornerFull = RoundedCornerShape(100)
7 | }
8 |
--------------------------------------------------------------------------------
/lib-components/src/main/java/bakuen/wear/components/wear/Modifier.kt:
--------------------------------------------------------------------------------
1 | package bakuen.wear.components.wear
2 |
3 | import androidx.compose.foundation.ScrollState
4 | import androidx.compose.foundation.focusable
5 | import androidx.compose.foundation.gestures.animateScrollBy
6 | import androidx.compose.foundation.gestures.scrollBy
7 | import androidx.compose.foundation.rememberScrollState
8 | import androidx.compose.foundation.verticalScroll
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.rememberCoroutineScope
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.focus.focusRequester
13 | import androidx.compose.ui.input.rotary.onRotaryScrollEvent
14 | import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
15 | import androidx.wear.compose.foundation.rememberActiveFocusRequester
16 | import kotlinx.coroutines.launch
17 |
18 | @OptIn(ExperimentalWearFoundationApi::class)
19 | @Composable
20 | fun Modifier.verticalRotaryScroll(state: ScrollState): Modifier {
21 | val coroutineScope = rememberCoroutineScope()
22 | return then(Modifier
23 | .verticalScroll(state)
24 | .onRotaryScrollEvent {
25 | coroutineScope.launch {
26 | state.scrollBy(it.verticalScrollPixels)
27 | state.animateScrollBy(0f)
28 | }
29 | true
30 | }
31 | .focusRequester(rememberActiveFocusRequester())
32 | .focusable())
33 | }
--------------------------------------------------------------------------------
/lib-navigator/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lib-navigator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.jetbrains.kotlin.android)
4 | alias(libs.plugins.compose.compiler)
5 | }
6 |
7 | android {
8 | namespace = "bakuen.lib.navigator"
9 | compileSdk = 34
10 |
11 | defaultConfig {
12 | minSdk = 21
13 | consumerProguardFiles("consumer-rules.pro")
14 | }
15 |
16 | buildTypes {
17 | release {
18 | isMinifyEnabled = false
19 | proguardFiles(
20 | getDefaultProguardFile("proguard-android-optimize.txt"),
21 | "proguard-rules.pro"
22 | )
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility = JavaVersion.VERSION_1_8
27 | targetCompatibility = JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = "1.8"
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation(platform(libs.androidx.compose.bom))
36 | implementation(libs.androidx.activity.compose)
37 | implementation(libs.androidx.compose.ui)
38 | implementation(libs.androidx.compose.animation)
39 | implementation(libs.androidx.compose.foundation)
40 | }
--------------------------------------------------------------------------------
/lib-navigator/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/lib-navigator/consumer-rules.pro
--------------------------------------------------------------------------------
/lib-navigator/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/lib-navigator/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib-navigator/src/main/java/bakuen/lib/navigator/ContentNode.kt:
--------------------------------------------------------------------------------
1 | package bakuen.lib.navigator
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 |
8 | open class ContentNode internal constructor(
9 | internal val content: @Composable () -> Unit,
10 | internal val key: Any? = null,
11 | ) {
12 | var enableSwipeBack by mutableStateOf(true)
13 | }
--------------------------------------------------------------------------------
/lib-navigator/src/main/java/bakuen/lib/navigator/Dialog.kt:
--------------------------------------------------------------------------------
1 | package bakuen.lib.navigator
2 |
3 | import android.content.res.Resources
4 | import androidx.compose.animation.AnimatedVisibility
5 | import androidx.compose.animation.EnterTransition
6 | import androidx.compose.animation.fadeIn
7 | import androidx.compose.animation.slideInVertically
8 | import androidx.compose.animation.slideOutHorizontally
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.CompositionLocalProvider
11 | import androidx.compose.runtime.LaunchedEffect
12 | import androidx.compose.runtime.mutableStateListOf
13 | import androidx.compose.runtime.staticCompositionLocalOf
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 |
17 | class DialogNode(
18 | content: @Composable () -> Unit,
19 | key: Any? = null
20 | ) : ContentNode(content, key) {
21 | fun dismiss() {
22 | DialogMan.dialogs.remove(this)
23 | }
24 | }
25 |
26 | val LocalDialog = staticCompositionLocalOf { error("") }
27 |
28 | object DialogMan {
29 | internal val dialogs = mutableStateListOf()
30 |
31 | fun push(key: Any? = null, content: @Composable () -> Unit) {
32 | dialogs.add(DialogNode(content = {
33 | val dialog = LocalDialog.current
34 | SwipeToDismiss(velocity = 16.dp, enabled = dialog.enableSwipeBack, onDismiss = { dialog.dismiss() }) {
35 | content()
36 | }
37 | }, key = key))
38 | }
39 |
40 | @Composable
41 | internal fun Dialogs() {
42 | dialogs.forEach { node ->
43 | CompositionLocalProvider(
44 | LocalDialog provides node,
45 | content = node.content
46 | )
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/lib-navigator/src/main/java/bakuen/lib/navigator/EventChannel.kt:
--------------------------------------------------------------------------------
1 | package bakuen.lib.navigator
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.collectAsState
5 | import kotlinx.coroutines.channels.Channel
6 | import kotlinx.coroutines.flow.receiveAsFlow
7 | import kotlinx.coroutines.runBlocking
8 |
9 | class EventChannel {
10 | private val channel = Channel(capacity = Channel.BUFFERED)
11 | private val flow = channel.receiveAsFlow()
12 | fun emit(value: T) {
13 | runBlocking {
14 | channel.send(value)
15 | }
16 | }
17 | @Composable
18 | fun consumeAsState() = flow.collectAsState(initial = null)
19 | }
--------------------------------------------------------------------------------
/lib-navigator/src/main/java/bakuen/lib/navigator/NavigationEvent.kt:
--------------------------------------------------------------------------------
1 | package bakuen.lib.navigator
2 |
3 | sealed class NavigationEvent {
4 | data object Backward : NavigationEvent()
5 | class Forward(val next: ContentNode) : NavigationEvent()
6 | class Replace(val target: ContentNode) : NavigationEvent()
7 | }
--------------------------------------------------------------------------------
/lib-navigator/src/main/java/bakuen/lib/navigator/NavigationWrapper.kt:
--------------------------------------------------------------------------------
1 | package bakuen.lib.navigator
2 |
3 | import androidx.compose.animation.core.Animatable
4 | import androidx.compose.animation.core.FloatExponentialDecaySpec
5 | import androidx.compose.foundation.gestures.Orientation
6 | import androidx.compose.foundation.gestures.draggable
7 | import androidx.compose.foundation.gestures.rememberDraggableState
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.BoxWithConstraints
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.offset
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.CompositionLocalProvider
14 | import androidx.compose.runtime.LaunchedEffect
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.rememberCoroutineScope
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.unit.IntOffset
19 | import kotlinx.coroutines.launch
20 | import kotlinx.coroutines.runBlocking
21 |
22 | @Composable
23 | internal fun NavigationWrapper(event: NavigationEvent?) {
24 | BoxWithConstraints(
25 | modifier = Modifier.fillMaxSize()
26 | ) {
27 | val maxOffset = constraints.maxWidth.toFloat()
28 | val swipeOffset = remember { Animatable(0f) }
29 | LaunchedEffect(event) {
30 | launch {
31 | when (event) {
32 | is NavigationEvent.Backward -> {
33 | swipeOffset.animateTo(maxOffset)
34 | Navigator.nodeBackward()
35 | swipeOffset.snapTo(0f)
36 | }
37 |
38 | is NavigationEvent.Forward -> {
39 | Navigator.nodeForward(event.next)
40 | swipeOffset.snapTo(maxOffset)
41 | swipeOffset.animateTo(0f)
42 | }
43 |
44 | is NavigationEvent.Replace -> {
45 | Navigator.nodeForward(event.target)
46 | swipeOffset.snapTo(maxOffset)
47 | swipeOffset.animateTo(0f)
48 | screens.removeAt(screens.lastIndex - 1)
49 | }
50 |
51 | else -> {}
52 | }
53 | }
54 | }
55 | val scope = rememberCoroutineScope()
56 | Box(
57 | modifier = Modifier
58 | .fillMaxSize()
59 | .run {
60 | if (screens.last().enableSwipeBack) {
61 | draggable(
62 | orientation = Orientation.Horizontal,
63 | state = rememberDraggableState {
64 | scope.launch {
65 | swipeOffset.snapTo((swipeOffset.value + it).coerceAtLeast(0f))
66 | }
67 | },
68 | onDragStopped = { velocity ->
69 | val targetValue =
70 | if (FloatExponentialDecaySpec().getTargetValue(
71 | swipeOffset.value, velocity
72 | ) > maxOffset / 2
73 | ) maxOffset else 0f
74 | swipeOffset.animateTo(targetValue)
75 | if (targetValue != 0f) {
76 | Navigator.nodeBackward()
77 | swipeOffset.snapTo(0f)
78 | }
79 | }
80 | )
81 | } else this
82 | }
83 | ) {
84 | screens.forEachIndexed { index, screen ->
85 | stateHolder.SaveableStateProvider(key = screen.hashCode()) {
86 | if (index >= screens.size - 2) {
87 | Box(Modifier.run {
88 | when (index) {
89 | screens.size - 2 -> {
90 | this.offset {
91 | IntOffset(
92 | x = ((-maxOffset + swipeOffset.value * 1f) * 0.3f).toInt(),
93 | y = 0
94 | )
95 | }
96 | }
97 |
98 | screens.size - 1 -> {
99 | this.offset {
100 | IntOffset(
101 | x = swipeOffset.value.toInt(),
102 | y = 0
103 | )
104 | }
105 | }
106 |
107 | else -> this
108 | }
109 | }) {
110 | CompositionLocalProvider(LocalScreen provides screen) {
111 | screenRoot(screen.content)
112 | }
113 | }
114 | }
115 | }
116 | }
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/lib-navigator/src/main/java/bakuen/lib/navigator/Navigator.kt:
--------------------------------------------------------------------------------
1 | package bakuen.lib.navigator
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import androidx.activity.compose.BackHandler
6 | import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.LaunchedEffect
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateListOf
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.saveable.SaveableStateHolder
13 | import androidx.compose.runtime.saveable.rememberSaveable
14 | import androidx.compose.runtime.saveable.rememberSaveableStateHolder
15 | import androidx.compose.runtime.setValue
16 | import androidx.compose.runtime.staticCompositionLocalOf
17 | import androidx.compose.ui.platform.LocalContext
18 | import bakuen.lib.navigator.Navigator.ReduceRecompose
19 | import bakuen.lib.navigator.Navigator.event
20 |
21 | val LocalScreen = staticCompositionLocalOf { error("") }
22 | internal lateinit var stateHolder: SaveableStateHolder
23 | internal lateinit var screenRoot: @Composable (currentScreen: @Composable () -> Unit) -> Unit
24 | internal val screens = mutableStateListOf()
25 |
26 | object Navigator {
27 |
28 | internal var event = EventChannel()
29 |
30 | @SuppressLint("Range")
31 | internal fun nodeBackward() {
32 | screens.removeLastOrNull()
33 | }
34 |
35 | internal fun nodeForward(node: ContentNode) {
36 | screens.add(node)
37 | }
38 |
39 | fun navigateBack(): Boolean {
40 | if (screens.size >= 2) {
41 | event.emit(NavigationEvent.Backward)
42 | return true
43 | } else return false
44 | }
45 |
46 | fun forward(key: Any? = null, screen: @Composable () -> Unit) {
47 | if (screens.last().key?.equals(key) == true) return
48 | event.emit(NavigationEvent.Forward(ContentNode(screen, key)))
49 | }
50 |
51 | fun replaceTop(screen: @Composable () -> Unit) {
52 | event.emit(NavigationEvent.Replace(ContentNode(screen)))
53 | }
54 |
55 | @Composable
56 | internal fun ReduceRecompose(content: @Composable () -> Unit) {
57 | content()
58 | }
59 | }
60 |
61 | @Composable
62 | fun NavHost(
63 | initScreen: @Composable () -> Unit,
64 | screenWrapper: @Composable (currentScreen: @Composable () -> Unit) -> Unit
65 | ) {
66 | stateHolder = rememberSaveableStateHolder()
67 | screenRoot = screenWrapper
68 | var isInit by rememberSaveable { mutableStateOf(true) }
69 | if (isInit) {
70 | screens.clear()
71 | screens.add(ContentNode(initScreen))
72 | isInit = false
73 | }
74 | ReduceRecompose {
75 | BackHandler(enabled = screens.size > 1) {
76 | Navigator.navigateBack()
77 | }
78 | val ac = (LocalContext.current as? Activity)
79 | LaunchedEffect(screens.size) {
80 | if (screens.size == 0) {
81 | ac?.finish()
82 | }
83 | }
84 | if (screens.isNotEmpty()) {
85 | NavigationWrapper(
86 | event = event.consumeAsState().value
87 | )
88 | }
89 | DialogMan.Dialogs()
90 | }
91 | }
--------------------------------------------------------------------------------
/lib-navigator/src/main/java/bakuen/lib/navigator/SwipeToDismiss.kt:
--------------------------------------------------------------------------------
1 | package bakuen.lib.navigator
2 |
3 | import android.content.res.Resources
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.animation.rememberSplineBasedDecay
6 | import androidx.compose.foundation.ExperimentalFoundationApi
7 | import androidx.compose.foundation.gestures.AnchoredDraggableState
8 | import androidx.compose.foundation.gestures.DraggableAnchors
9 | import androidx.compose.foundation.gestures.Orientation
10 | import androidx.compose.foundation.gestures.anchoredDraggable
11 | import androidx.compose.foundation.layout.Box
12 | import androidx.compose.foundation.layout.BoxScope
13 | import androidx.compose.foundation.layout.offset
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.platform.LocalDensity
18 | import androidx.compose.ui.unit.Dp
19 | import androidx.compose.ui.unit.IntOffset
20 |
21 | private enum class DragAnchors {
22 | Normal,
23 | Dismiss,
24 | }
25 |
26 | @OptIn(ExperimentalFoundationApi::class)
27 | @Composable
28 | fun SwipeToDismiss(
29 | modifier: Modifier = Modifier,
30 | enabled: Boolean = true,
31 | velocity: Dp,
32 | onDismiss: ()->Unit,
33 | reverse: Boolean = false,
34 | content: @Composable BoxScope.()->Unit
35 | ) {
36 | if (enabled) {
37 | val reverseSymbol = if (reverse) -1 else 1
38 | val maxOffset = reverseSymbol * Resources.getSystem().displayMetrics.widthPixels.toFloat()
39 | val density = LocalDensity.current
40 | val decay = rememberSplineBasedDecay()
41 | val state = remember {
42 | AnchoredDraggableState(
43 | initialValue = DragAnchors.Normal,
44 | positionalThreshold = { totalDistance ->
45 | totalDistance * 0.5f
46 | },
47 | velocityThreshold = {
48 | with(density) {
49 | velocity.toPx()
50 | }
51 | },
52 | snapAnimationSpec = tween(),
53 | decayAnimationSpec = decay,
54 | anchors = DraggableAnchors {
55 | DragAnchors.Normal at 0f
56 | DragAnchors.Dismiss at maxOffset
57 | }
58 | )
59 | }
60 | Box(
61 | modifier = modifier
62 | .offset {
63 | val x = state.requireOffset()
64 | if (x == maxOffset) onDismiss()
65 | IntOffset(x = x.toInt(), y = 0)
66 | }
67 | .anchoredDraggable(
68 | state = state,
69 | orientation = Orientation.Horizontal,
70 | ),
71 | content = content
72 | )
73 | } else Box(modifier = modifier, content = content)
74 | }
--------------------------------------------------------------------------------
/lib-protostore/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lib-protostore/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.jetbrains.kotlin.android)
4 | alias(libs.plugins.kotlinx.serialization)
5 | alias(libs.plugins.compose.compiler)
6 | }
7 |
8 | android {
9 | namespace = "bakuen.lib.protostore"
10 | compileSdk = 34
11 |
12 | defaultConfig {
13 | minSdk = 21
14 |
15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles("consumer-rules.pro")
17 | }
18 |
19 | buildTypes {
20 | release {
21 | isMinifyEnabled = false
22 | proguardFiles(
23 | getDefaultProguardFile("proguard-android-optimize.txt"),
24 | "proguard-rules.pro"
25 | )
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility = JavaVersion.VERSION_1_8
30 | targetCompatibility = JavaVersion.VERSION_1_8
31 | }
32 | kotlinOptions {
33 | jvmTarget = "1.8"
34 | }
35 | }
36 |
37 | dependencies {
38 | implementation(platform(libs.androidx.compose.bom))
39 | implementation(libs.androidx.compose.ui)
40 | api(libs.kotlinx.serialization.protobuf)
41 | }
--------------------------------------------------------------------------------
/lib-protostore/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/java30433/TaskManager/1bca47ad3cba310723929a231a5e7cb4d4dc9434/lib-protostore/consumer-rules.pro
--------------------------------------------------------------------------------
/lib-protostore/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/lib-protostore/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/lib-protostore/src/main/java/bakuen/lib/protostore/ContextProvider.kt:
--------------------------------------------------------------------------------
1 | package bakuen.lib.protostore
2 |
3 | import android.content.ContentProvider
4 | import android.content.ContentValues
5 | import android.content.Context
6 | import android.database.Cursor
7 | import android.net.Uri
8 |
9 | lateinit var appContext: Context
10 | private set
11 | class ContextProvider : ContentProvider() {
12 | override fun onCreate(): Boolean {
13 | appContext = this.context!!
14 | return true
15 | }
16 |
17 | override fun query(
18 | p0: Uri,
19 | p1: Array?,
20 | p2: String?,
21 | p3: Array?,
22 | p4: String?
23 | ): Cursor? {
24 | return null
25 | }
26 |
27 | override fun getType(p0: Uri): String? {
28 | return null
29 | }
30 |
31 | override fun insert(p0: Uri, p1: ContentValues?): Uri? {
32 | return null
33 | }
34 |
35 | override fun delete(p0: Uri, p1: String?, p2: Array?): Int {
36 | return 0
37 | }
38 |
39 | override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array?): Int {
40 | return 0
41 | }
42 | }
--------------------------------------------------------------------------------
/lib-protostore/src/main/java/bakuen/lib/protostore/ProtoStore.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UNCHECKED_CAST")
2 | @file:OptIn(ExperimentalSerializationApi::class)
3 |
4 | /**
5 | * 早晚得把这个库重写了
6 | */
7 | package bakuen.lib.protostore
8 |
9 | import android.content.Context
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.LaunchedEffect
12 | import androidx.compose.runtime.MutableState
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.ui.platform.LocalInspectionMode
16 | import kotlinx.serialization.ExperimentalSerializationApi
17 | import kotlinx.serialization.KSerializer
18 | import kotlinx.serialization.protobuf.ProtoBuf
19 | import java.io.File
20 |
21 | val protobuf = ProtoBuf { }
22 | class SerializerCache(val serializer: KSerializer, val name: String, var store: MutableState? = null)
23 | val serializerCache = mutableMapOf, SerializerCache<*>>()
24 | interface ProtoStore
25 |
26 | inline fun getSerializer(): SerializerCache {
27 | return serializerCache.getOrPut(T::class.java) {
28 | val companion = T::class.java.getDeclaredField("Companion").get(null)
29 | val serializer = companion.javaClass.getDeclaredMethod("serializer").invoke(companion) as KSerializer
30 | SerializerCache(serializer, serializer.descriptor.serialName)
31 | } as SerializerCache
32 | }
33 | @PublishedApi
34 | internal inline fun getStoreState(): MutableState {
35 | val cache = getSerializer()
36 | if (cache.store == null) {
37 | cache.store = mutableStateOf(protobuf.decodeFromByteArray(cache.serializer, appContext.getFile(cache.name).readBytes()))
38 | }
39 | return cache.store!!
40 | }
41 | inline fun getStore(): T = getStoreState().value
42 | inline fun writeStore(value: T) {
43 | val cache = getSerializer()
44 | if (cache.store == null) cache.store = mutableStateOf(value)
45 | else cache.store!!.value = value
46 | appContext.getFile(cache.name).writeBytes(protobuf.encodeToByteArray(cache.serializer, value))
47 | }
48 | inline fun setStore(block: (T)->T) {
49 | writeStore(block(getStore()))
50 | }
51 |
52 | @Composable
53 | inline fun rememberStore(preview: T? = null): MutableState {
54 | if (preview != null && LocalInspectionMode.current) return remember { mutableStateOf(preview) }
55 | val state = getStoreState()
56 | LaunchedEffect(state.value) {
57 | writeStore(state.value)
58 | }
59 | return state
60 | }
61 |
62 | fun Context.getFile(name: String): File {
63 | val f = File(filesDir, name)
64 | if (!f.exists()) f.createNewFile()
65 | return f
66 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | maven("https://jitpack.io")
20 | }
21 | }
22 |
23 | rootProject.name = "TaskManager"
24 | include(":app")
25 | include(":lib-components")
26 | include(":lib-navigator")
27 | include(":lib-protostore")
28 |
--------------------------------------------------------------------------------