diff --git a/Animora/.github/workflows/generateAnimora.yml b/Animora/.github/workflows/generateAnimora.yml
new file mode 100644
index 0000000000..b955f5aba8
--- /dev/null
+++ b/Animora/.github/workflows/generateAnimora.yml
@@ -0,0 +1,39 @@
+name: Animora CI/CD
+
+run-name: "Building Animora APK And Release"
+
+on:
+ push:
+ tags:
+ - v*.*.*
+
+permissions:
+ contents: write
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup JDK 17 + Gradle cache
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: 17
+ cache: gradle
+
+ - name: Grant execute permission
+ run: chmod +x gradlew
+
+ - name: Build Debug APK (MVP)
+ run: ./gradlew assembleDebug --no-daemon
+
+ - name: Upload Release
+ uses: softprops/action-gh-release@v2
+ if: github.ref_type == 'tag'
+ with:
+ files: app/build/outputs/apk/debug/app-debug.apk
+ body_path: releases/${{ github.ref_name }}.md
\ No newline at end of file
diff --git a/Animora/.gitignore b/Animora/.gitignore
new file mode 100644
index 0000000000..415fc8ac3c
--- /dev/null
+++ b/Animora/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+# Kotlin
+.kotlin
+
+# Android Studio
+.idea
\ No newline at end of file
diff --git a/Animora/LICENSE b/Animora/LICENSE
new file mode 100644
index 0000000000..bfef380bf7
--- /dev/null
+++ b/Animora/LICENSE
@@ -0,0 +1,437 @@
+Attribution-NonCommercial-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
+Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-NonCommercial-ShareAlike 4.0 International Public License
+("Public License"). To the extent this Public License may be
+interpreted as a contract, You are granted the Licensed Rights in
+consideration of Your acceptance of these terms and conditions, and the
+Licensor grants You such rights in consideration of benefits the
+Licensor receives from making the Licensed Material available under
+these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-NC-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution, NonCommercial, and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. NonCommercial means not primarily intended for or directed towards
+ commercial advantage or monetary compensation. For purposes of
+ this Public License, the exchange of the Licensed Material for
+ other material subject to Copyright and Similar Rights by digital
+ file-sharing or similar means is NonCommercial provided there is
+ no payment of monetary compensation in connection with the
+ exchange.
+
+ l. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ m. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ n. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part, for NonCommercial purposes only; and
+
+ b. produce, reproduce, and Share Adapted Material for
+ NonCommercial purposes only.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties, including when
+ the Licensed Material is used other than for NonCommercial
+ purposes.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-NC-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database for NonCommercial purposes
+ only;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
\ No newline at end of file
diff --git a/Animora/README.md b/Animora/README.md
new file mode 100644
index 0000000000..521b98fa59
--- /dev/null
+++ b/Animora/README.md
@@ -0,0 +1,109 @@
+# Animora
+
+[本文中文版](README_zh-CN.md)
+
+An open source Material Design Animation Textbook and Teaching Equipment
+
+## Preview
+
+
+
+
+
+
+
+
+
+
+
+## Features
+
+- Animation Gallery: A centralized list showcasing a wide variety of Jetpack Compose animation effects.
+- Interactive Demos: Users can directly interact with animation samples to intuitively experience how they work.
+- Categorized Browsing: Animations are organized by type and complexity for easy discovery and learning.
+- Detail Views: Each animation has a dedicated detail page with more in-depth demonstrations and explanations.
+- Modern UI Design: Built entirely with Jetpack Compose, following Material Design principles for a sleek and smooth interface.
+- Code References: Provides core implementation code snippets for each animation, enabling developers to learn and apply them quickly.
+- 100% Kotlin: The entire project is written in Kotlin.
+
+## Future planning
+
+- [x] Basic Feature Available
+- [x] Add necessary SpringSpec API discovery
+- [x] Add sharedTransition API for list view
+- [x] Add more annotations for the project
+- [x] Use CI/CD for best practice
+- [ ] Upload the application to F-Droid
+- [ ] Upload the application to GooglePlay
+
+## Why I Made Animora?
+
+Because I fell in love with Material Design long before I became an Android developer.
+
+When I was still a student, Google’s **Material You** design language deeply resonated with me.
+I had always hoped that one day, I could build a **Material Design app of my own** — not as a demo, not as a concept, but as a real, usable product.
+
+Animora is the result of that long-standing aspiration.
+
+---
+
+Because I believe technology should be equal and transparent.
+
+My first online name was **Free-Terder**, literally meaning *“Free Trader”*.
+It was built upon a single belief that has stayed with me for many years:
+
+> **Truth will eventually pierce lies, and technology should belong to everyone.**
+
+This belief accompanied me from Android ROM flashing, through Linux fundamentals, and finally to software development itself.
+In 2024, I made a firm decision to become an **Android Developer**, and Animora is a concrete step forward on that path.
+
+---
+
+Because learning Android should not feel hopeless.
+
+While learning **Kotlin**, **Android development**, and **Jetpack Compose**,
+this belief repeatedly pulled me back from frustration and self-doubt.
+
+Animora was created during those moments —
+as something I wished had existed when I was learning animations, state, and interaction in Compose.
+
+---
+
+Because doing the right thing is harder than doing the reasonable thing.
+
+While preparing Animora for release, I was reading *Silo*, which left a strong impression on me:
+
+> **Doing the right thing is not the same as doing the reasonable thing.
+> And between the two, the reasonable choice is always easier.**
+
+Still, I believe the light of human civilization is real.
+There are always people willing to carry the weight and choose what is right —
+and I choose to stand among them.
+
+---
+
+Because I chose open source, deliberately.
+
+I did not choose the common path of **closed source + paid access**.
+That is simply not the future I want to see.
+
+Closed information barriers breed ignorance.
+Animora exists — and is fully open source — precisely because I want to resist that outcome.
+
+---
+
+Because I want Animora to help more people.
+
+- To help struggling developers **feel less alone**, and have something reliable to lean on
+- To help students learning *Java for Android* **realize early that academic knowledge is often outdated or incomplete**
+- To contribute, even in a small way, to a **healthier and more honest open-source ecosystem**
+- To find like-minded people and **build things together**
+
+---
+
+In a word, Animora tries to do the right thing —
+with clean code, modern Android practices, and an honest intent.
+
+---
+
+### For those who still have the courage to choose hope.
\ No newline at end of file
diff --git a/Animora/README_zh-CN.md b/Animora/README_zh-CN.md
new file mode 100644
index 0000000000..23aa29388d
--- /dev/null
+++ b/Animora/README_zh-CN.md
@@ -0,0 +1,113 @@
+# Animora
+
+[For English Version](README.md)
+
+一个开源的 Material Design 动画教材与教学工具
+
+## 预览
+
+
+
+
+
+
+
+
+
+
+## 功能特性
+
+- 动画画廊:集中展示多种 Jetpack Compose 动画效果,便于系统性学习。
+- 可交互示例:用户可以直接与动画示例交互,直观理解动画的运行方式。
+- 分类浏览:按动画类型与复杂度进行分类,方便查找与循序渐进地学习。
+- 详情页面:每个动画都配有独立的详情页,提供更深入的演示与讲解。
+- 现代化 UI 设计:完全基于 Jetpack Compose 构建,遵循 Material Design 设计规范,界面简洁流畅。
+- 代码参考:为每个动画提供核心实现代码,帮助开发者快速理解并应用。
+- 100% Kotlin:整个项目完全使用 Kotlin 编写。
+
+## 未来规划
+
+- [x] 基础功能已完成
+- [x] 补充必要的 SpringSpec API 探索
+- [x] 为列表视图引入 SharedTransition API
+- [x] 为项目补充更多注释与文档说明
+- [x] 使用 CI/CD 作为最佳实践
+- [ ] 将应用发布到 F-Droid
+- [ ] 将应用发布到 Google Play
+
+## 为什么我做了 Animora?
+
+因为我很早就喜欢上了 Material Design。
+
+在我还是学生的时候,就被 Google 的 **Material You** 设计语言深深吸引。
+那时我就希望,有一天能亲手做出一款真正属于自己的 **Material Design 应用**——
+不是概念,不是练习,而是一个真实、可用、经得起推敲的产品。
+
+Animora,正是这个愿望的结果。
+
+---
+
+因为我相信:真相终将戳破谎言,科技应人人平等。
+
+我的第一个网名是 **Free-Terder**,意为「免费商人」。
+它存在的理由,始终只有一句话:
+
+> **真相终将戳破谎言,科技应人人平等**
+
+这句座右铭陪伴了我很多年——
+从折腾手机系统,到学习 Linux 基础,再到真正走上软件开发这条路。
+直到今年,我终于下定决心,成为一名 **Android Developer**,
+而 Animora,是我迈出的重要一步。
+
+---
+
+因为学习 Android,不应该让人感到绝望。
+
+在学习 **Kotlin、Android 开发** 与 **Jetpack Compose** 的过程中,
+这句座右铭一次次把我从迷茫和挫败中拉回来。
+
+Animora,正是诞生在这些时刻。
+它是我在学习动画、状态与交互时,**最希望当时就能拥有的工具与示例集合**。
+
+---
+
+因为做正确的事,往往比做合理的事更难。
+
+在 Animora 发布前夕,我正在阅读《**Silo**》,其中一句话让我印象深刻:
+
+> **做正确的事,并不等同于做合理的事;
+> 而在二者之间,后者显然更容易。**
+
+但我依然相信,
+属于人类文明的光辉是真实存在的。
+这个世界从不缺少那些肩负重担、依然选择做正确之事的人。
+我也愿意成为其中一员。
+
+---
+
+因为我有意选择了开源。
+
+我没有选择闭源 + 收费的道路。
+这并不是我愿意看到的未来。
+
+封闭的信息壁垒容易滋生愚昧,
+而 Animora 的完全开源,正是对这种趋势的一次微小反抗。
+
+---
+
+因为我希望 Animora 能帮助更多人。
+
+- 让正在苦苦挣扎的开发者,**少一些焦虑,多一份可以信赖的依靠**
+- 让正在学习 *Java for Android* 的学生,**尽早意识到:学校里的知识并不总是正确的**
+- 尽自己微薄的力量,**推动一个更加开放、真实、健康的开源社区**
+- 找到志同道合的人,**一起并肩作战**
+
+---
+
+总而言之,Animora 想做的事情很简单:
+**遵循最佳实践,保持代码整洁,做正确的事。**
+
+---
+
+### 为了那些
+**依然有勇气选择希望的人**
\ No newline at end of file
diff --git a/Animora/app/.gitignore b/Animora/app/.gitignore
new file mode 100644
index 0000000000..67aaf579a1
--- /dev/null
+++ b/Animora/app/.gitignore
@@ -0,0 +1,3 @@
+/build
+/release
+/debug
\ No newline at end of file
diff --git a/Animora/app/build.gradle.kts b/Animora/app/build.gradle.kts
new file mode 100644
index 0000000000..5f6aac03b8
--- /dev/null
+++ b/Animora/app/build.gradle.kts
@@ -0,0 +1,79 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.compose)
+ kotlin("plugin.serialization") version "2.0.21"
+}
+
+android {
+ namespace = "com.baidaidai.animora"
+ compileSdk = 35
+
+ defaultConfig {
+ applicationId = "com.baidaidai.animora"
+ minSdk = 24
+ targetSdk = 35
+ versionCode = 1
+ versionName = "1.2.8"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+ buildFeatures {
+ compose = true
+ }
+}
+
+dependencies {
+
+ testImplementation("junit:junit:4.13.2")
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.material3)
+ implementation("androidx.compose.material3:material3-adaptive-navigation-suite")
+ implementation(libs.material)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+ debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.8")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")
+ implementation("androidx.navigation:navigation-compose:2.8.6")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
+ implementation("androidx.window:window:1.5.0")
+ implementation("androidx.compose.material:material-icons-core:1.7.5")
+ implementation("androidx.compose.material:material-icons-extended:1.7.5")
+ implementation("androidx.compose.material3:material3:1.4.0-alpha14")
+ implementation("com.squareup.okhttp3:okhttp:4.12.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
+ implementation("androidx.compose.ui:ui:1.9.0")
+ implementation("androidx.compose.ui:ui-graphics:1.9.0")
+ implementation("androidx.compose.ui:ui-tooling-preview:1.9.0")
+ implementation("androidx.core:core-splashscreen:1.0.0")
+ implementation("com.github.jeziellago:compose-markdown:0.5.8")
+
+}
\ No newline at end of file
diff --git a/Animora/app/proguard-rules.pro b/Animora/app/proguard-rules.pro
new file mode 100644
index 0000000000..481bb43481
--- /dev/null
+++ b/Animora/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/Animora/app/src/androidTest/java/com/baidaidai/animora/ExampleInstrumentedTest.kt b/Animora/app/src/androidTest/java/com/baidaidai/animora/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000000..8b3dab57a5
--- /dev/null
+++ b/Animora/app/src/androidTest/java/com/baidaidai/animora/ExampleInstrumentedTest.kt
@@ -0,0 +1,74 @@
+package com.baidaidai.animora
+
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.compose.rememberNavController
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.baidaidai.animora.components.StartScreen.components.NecessaryComponents
+import com.baidaidai.animora.shared.viewModel.animationDatasViewModel
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+import org.junit.Rule
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ @Test
+ fun navigate_to_list_view(){
+ composeTestRule.setContent {
+ val animationDetailsViewModel = viewModel()
+ CompositionLocalProvider(
+ LocalAnimationViewModel provides animationDetailsViewModel
+ ) {
+ AnimoraApp()
+ }
+ }
+ composeTestRule.onNodeWithContentDescription(
+ label = "List",
+ useUnmergedTree = true
+ ).performClick()
+ composeTestRule.onNodeWithText("shareBorder").assertExists()
+ }
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ @Test
+ fun navigate_to_info_activity_and_back(){
+ composeTestRule.setContent {
+ val animationDetailsViewModel = viewModel()
+ CompositionLocalProvider(
+ LocalAnimationViewModel provides animationDetailsViewModel
+ ) {
+ AnimoraApp()
+ }
+ }
+ composeTestRule.onNodeWithContentDescription(
+ label = "Info",
+ useUnmergedTree = true
+ ).performClick()
+ composeTestRule.onNodeWithText("About").assertExists()
+ composeTestRule.onNodeWithContentDescription(
+ label = "Back",
+ useUnmergedTree = true
+ ).performClick()
+ composeTestRule.onNodeWithText("Home").assertExists()
+ }
+
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/AndroidManifest.xml b/Animora/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..61602619f1
--- /dev/null
+++ b/Animora/app/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Animora/app/src/main/assets/markdown/CubicBezierEasing.md b/Animora/app/src/main/assets/markdown/CubicBezierEasing.md
new file mode 100644
index 0000000000..8ca358c9b5
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/CubicBezierEasing.md
@@ -0,0 +1,21 @@
+### 是什么?
+
+CubicBezierEasing是一种基于时间的动画缓动曲线 (Easing) API。
+- 三次贝塞尔缓动曲线(CubicBezierEasing)是一种通过定义两个控制点来精确描述动画速度变化曲线的数学工具。
+- 与预设的缓动曲线(如线性、加速、减速)不同,自定义贝塞尔曲线允许开发者创造出高度个性化的动画节奏和非线性运动轨迹。
+- 这意味着您可以根据设计需求,让动画在某个阶段加速、减速,或者在开始和结束时呈现独特的“吸附”或“回弹”效果,从而实现更富有创意和表现力的动画。
+- 这种精细控制能力使得动画不再受限于固定模式,能够完美匹配产品的品牌调性和用户体验要求。
+
+这个 API 接受4个核心输入:a, b, c, d。
+- a, b, c, d这四个参数共同定义了三次贝塞尔曲线的两个控制点 (a, b) 和 (c, d),它们决定了动画速度的变化曲线。
+这四个参数应共同使用,类似于:`CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)`
+
+当这四个参数改变时,CubicBezierEasing 会生成不同的速度曲线,从而让动画呈现出加速、减速、回弹等高度定制化的节奏效果。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 创建一个自定义的“缓入缓出”效果,使动画的开始和结束都非常平滑。
+2. 设计一个物体被“扔”出屏幕的动画,初始速度很快,然后逐渐减速。
+3. 完美复刻设计工具(如Figma、After Effects)中定义的精确缓动曲线,实现像素级的设计还原。
diff --git a/Animora/app/src/main/assets/markdown/animatable.md b/Animora/app/src/main/assets/markdown/animatable.md
new file mode 100644
index 0000000000..3b337a6228
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/animatable.md
@@ -0,0 +1,21 @@
+### 是什么?
+
+Animatable是一种协程驱动的单值动画 API。
+- Animatable是Compose中一个强大的、基于协程的低级别单值动画API,它提供对动画过程更细粒度的控制能力。
+- 与高级别的`animate*AsState`系列API不同,Animatable允许开发者直接通过挂起函数`animateTo`来改变其内部维护的动画值,并且可以在动画执行过程中进行暂停、中断、重启甚至反向播放等复杂操作。
+- 这使得Animatable非常适合实现手势驱动的动画,例如用户拖拽元素时动画实时响应,或者需要精确控制动画序列和同步的场景。
+- 它提供了强大的灵活性,让开发者能够构建出高度定制化和交互性强的动画效果,满足那些超越标准过渡效果的需求。
+
+这个 API 接受1个核心输入:targetValue。
+targetValue表示动画要运行到的目标值。
+- targetValue应使用类似于:在 `Animatable` 实例上调用 `anim.animateTo(1f)` 的参数
+
+当调用 animateTo 函数时,Animatable 会从当前值向 targetValue 发起一个动画,这个过程可以被中断和重启。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 实现由用户手势(如拖拽、滑动)驱动的实时动画反馈。
+2. 构建复杂的、可中断的动画序列,例如一个元素移动到某处,然后根据用户输入改变方向。
+3. 在需要精确控制动画生命周期的场景,如手动启动、暂停或捕捉动画过程中的速度。
diff --git a/Animora/app/src/main/assets/markdown/animateColor.md b/Animora/app/src/main/assets/markdown/animateColor.md
new file mode 100644
index 0000000000..165acbb282
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/animateColor.md
@@ -0,0 +1,22 @@
+### 是什么?
+
+animateColor是一种无限循环的颜色动画 API。
+- `animateColor`是Compose `rememberInfiniteTransition`的扩展,专门用于创建无限循环的颜色动画。
+- 它允许您定义一个起始颜色和一个或多个目标颜色,并指定动画规范(如`tween`或`spring`),然后它会自动在这些颜色之间进行平滑、持续的循环过渡。
+- 不同于`animateColorAsState`的一次性过渡,`animateColor`会无限期地重复播放颜色变化,非常适合实现背景渐变、呼吸灯效果、闪烁提示或任何需要持续动态色彩变化的UI元素。
+- 通过与`rememberInfiniteTransition`配合使用,它简化了复杂无限循环颜色动画的实现,让开发者能够轻松为应用注入生动的色彩活力,提升视觉吸引力。
+
+这个 API 接受2个核心输入:initialValue, targetValue。
+initialValue, targetValue表示动画的起始和目标颜色。
+- initialValue应使用类似于:`initialValue = Color.Red` 的参数
+- targetValue应使用类似于:`targetValue = Color.Green` 的参数
+
+当定义了这两个值后,animateColor 会自动在这些颜色之间进行平滑、持续的循环过渡。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 为应用的背景创建一个平滑、无限循环的颜色渐变效果。
+2. 实现一个“呼吸灯”效果,例如一个图标的颜色在明暗之间规律变化。
+3. 创建一个引人注目的、持续闪烁的警告或提示信息。
diff --git a/Animora/app/src/main/assets/markdown/animateColorAsState.md b/Animora/app/src/main/assets/markdown/animateColorAsState.md
new file mode 100644
index 0000000000..2b3ae386f3
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/animateColorAsState.md
@@ -0,0 +1,21 @@
+### 是什么?
+
+animateColorAsState是一种状态驱动的单值动画 API。
+- animateColorAsState是一个强大的Compose单值动画API,它能够根据您提供的布尔状态,在两种指定的颜色之间实现平滑且自然的过渡。
+- 它会自动处理颜色插值的复杂性,无需您手动计算中间值或编写复杂的动画逻辑。
+- 当组件的某个状态改变需要伴随背景色或其他颜色属性的渐变时,animateColorAsState会实时响应,以优雅的方式更新颜色,从而极大地提升用户界面的动态性和视觉吸引力。
+- 它使得即使是非专业动画开发者也能轻松实现专业级的颜色动画效果,为用户提供流畅的视觉反馈,确保每一次状态切换都充满生命力。
+
+这个 API 接受1个核心输入:targetValue。
+targetValue表示动画的目标状态。
+- targetValue应使用类似于:`val enabled by remember { mutableStateOf(true) }` 的参数
+
+当targetValue改变时,animateColorAsState 会根据您提供的布尔状态,在两种指定的颜色之间实现平滑且自然的过渡。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 当按钮被按下时,平滑地改变其背景颜色。
+2. 根据状态(如在线/离线)为用户头像的边框应用不同的颜色过渡。
+3. 在可选择的列表项被选中时,为其背景色添加一个优雅的渐变动画。
diff --git a/Animora/app/src/main/assets/markdown/animateContentSize.md b/Animora/app/src/main/assets/markdown/animateContentSize.md
new file mode 100644
index 0000000000..5331ba7386
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/animateContentSize.md
@@ -0,0 +1,18 @@
+### 是什么?
+
+animateContentSize是一种由内容尺寸变化驱动的布局动画 Modifier。
+- animateContentSize是Jetpack Compose中的一个强大且实用的Modifier,它能够自动为Composable组件的尺寸变化提供平滑的动画过渡效果。
+- 当组件内部的内容发生改变,导致其所需的空间大小发生增减时,animateContentSize会自动检测这些尺寸差异,并生成一个优雅的动画来调整组件的边界,避免了内容突变带来的生硬和视觉不适。
+- 这对于那些动态加载内容、根据数据展开或收缩的UI元素来说尤为重要,它能够显著提升用户体验,使得界面的变化更加流畅和直观。
+
+这个 API 接受0个核心输入:。
+
+- 当内容尺寸改变时,animateContentSize 会自动检测这些尺寸差异,并生成一个优雅的动画来调整组件的边界。
+
+· 适用于:
+
+这个API可以适用于一下情况
+
+1. 可展开/收起的卡片,点击后平滑地显示或隐藏更多内容。
+2. 文本框根据输入内容的多少自动、平滑地调整其高度。
+3. 动态加载的图片或内容,其容器尺寸平滑地适应内容大小。
diff --git a/Animora/app/src/main/assets/markdown/animateContentVisibility.md b/Animora/app/src/main/assets/markdown/animateContentVisibility.md
new file mode 100644
index 0000000000..cf9a48bfbe
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/animateContentVisibility.md
@@ -0,0 +1,20 @@
+### 是什么?
+
+animateContentVisibility是一种状态驱动的内容可见性动画 API。
+- animateContentVisibility是Compose中一个非常方便的容器Composable,它通过一个布尔型的`visible`状态参数,来控制其子内容是否显示以及在显示/隐藏时的过渡动画。
+- 当`visible`状态从`false`变为`true`时,它可以应用预定义的进入动画(如淡入、滑动),使内容优雅地出现在屏幕上;反之,当`visible`从`true`变为`false`时,它会执行退出动画,使内容平滑地消失。
+- 这个API极大地简化了UI元素显隐逻辑的实现,开发者无需手动管理动画状态或布局变化,只需关注内容的可见性,animateContentVisibility就能自动处理复杂的动画效果,从而显著提升界面交互的流畅度和用户体验。
+
+这个 API 接受1个核心输入:visible。
+visible表示一个布尔值,用于控制其子内容是否显示。
+visible应使用类似于:`val visible by remember { mutableStateOf(true) }` 的参数
+
+- 当visible改变时,animateContentVisibility 会应用预定义的进入或退出动画(如淡入、滑动),使内容优雅地出现或消失在屏幕上。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 当某个条件满足时,平滑地显示或隐藏一个提示信息或错误消息。
+2. 在加载数据时,使用淡入淡出效果展示一个加载指示器。
+3. 根据用户的滚动行为,平滑地显示或隐藏一个浮动操作按钮(FAB)。
diff --git a/Animora/app/src/main/assets/markdown/animateFloat.md b/Animora/app/src/main/assets/markdown/animateFloat.md
new file mode 100644
index 0000000000..2681880c22
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/animateFloat.md
@@ -0,0 +1,23 @@
+### 是什么?
+
+animateFloat是一种无限循环的浮点值动画 API。
+- `animateFloat`是Compose `rememberInfiniteTransition`的又一个强大扩展,它专注于创建无限循环的浮点数值动画。
+- 通过定义一个起始浮点值和一个目标浮点值,并结合一个动画规范,`animateFloat`能够在这些值之间持续不断地进行平滑循环过渡。
+- 由于浮点数可以代表多种视觉属性(如尺寸比例、透明度、旋转角度、平移距离等),`animateFloat`成为了驱动各种持续动态效果的灵活工具。
+- 开发者可以利用它轻松实现元素的持续缩放、淡入淡出、旋转、来回移动等无限循环动画,而无需手动管理复杂的动画状态和循环逻辑。
+- 它极大地丰富了Compose界面的表现力,为用户提供持续的视觉反馈和沉浸式体验。
+
+这个 API 接受2个核心输入:initialValue, targetValue。
+initialValue, targetValue表示动画的起始和目标浮点值。
+- initialValue应使用类似于:`initialValue = 0f` 的参数
+- targetValue应使用类似于:`targetValue = 360f` 的参数
+
+当定义了这两个值后,animateFloat 会在这些值之间持续不断地进行平滑循环过渡,用于驱动各种视觉属性。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 通过驱动 `rotate` 修饰符,创建一个无限旋转的加载动画。
+2. 通过驱动 `scale` 修饰符,实现一个持续放大和缩小的“脉冲”效果。
+3. 通过驱动 `alpha` 修饰符,让一个UI元素实现无限循环的淡入淡出效果。
diff --git a/Animora/app/src/main/assets/markdown/animateOpacityAsState.md b/Animora/app/src/main/assets/markdown/animateOpacityAsState.md
new file mode 100644
index 0000000000..8c2d709338
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/animateOpacityAsState.md
@@ -0,0 +1,21 @@
+### 是什么?
+
+animateOpacityAsState是一种状态驱动的单值动画 API。
+- animateOpacityAsState是Jetpack Compose中专门用于处理透明度(Opacity)变化的单值动画API。
+- 它基于一个浮点数状态(通常在0f到1f之间),当目标透明度值发生变化时,该API能够自动在起始透明度和目标透明度之间创建并执行一个平滑的插值动画。
+- 这意味着开发者可以轻松地实现UI元素的淡入、淡出、闪烁等视觉效果,而无需手动编写复杂的插值逻辑或动画控制器。
+- 它的轻量级特性使其成为快速响应用户交互或状态更新时,提供优雅透明度过渡动画的理想选择,从而提升整个应用的视觉流畅性和用户的感官体验。
+
+这个 API 接受1个核心输入:targetValue。
+targetValue表示一个浮点数,代表目标透明度。
+- targetValue应使用类似于:`val opacity by remember { mutableStateOf(1f) }` 的参数
+
+当targetValue改变时,animateOpacityAsState 会自动在起始透明度和目标透明度之间创建并执行一个平滑的插值动画。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 实现UI元素的淡入或淡出效果,使其出现和消失更加自然。
+2. 当一个按钮或控件被禁用时,通过降低其透明度来提供视觉反馈。
+3. 为一个需要用户注意的元素(如新消息提示)创建轻微的闪烁或呼吸效果。
diff --git a/Animora/app/src/main/assets/markdown/infiniteRepeatable.md b/Animora/app/src/main/assets/markdown/infiniteRepeatable.md
new file mode 100644
index 0000000000..966cba9212
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/infiniteRepeatable.md
@@ -0,0 +1,21 @@
+### 是什么?
+
+infiniteRepeatable是一种无限重复动画包装器 (AnimationSpec) API。
+- `infiniteRepeatable`是Compose动画中一个非常实用的`AnimationSpec`包装器,它的主要作用是将任何一个标准的动画规范(如`tween`、`spring`等)转换为一个可以无限循环播放的动画。
+- 这意味着一旦动画开始,它将持续不断地重复其定义的序列,直到Composable被移除或状态不再触发动画。
+- 这个API非常适合用于实现那些需要持续进行的背景动画、加载指示器、闪烁效果或者任何需要不断重复的视觉元素,为用户提供持续的视觉反馈。
+- 它简化了无限循环动画的实现,开发者无需手动管理循环逻辑,只需定义一次动画,`infiniteRepeatable`就会自动处理其无限重复播放,从而提升了用户界面的动态感和活跃度。
+
+这个 API 接受1个核心输入:animation。
+animation表示一个标准的动画规范(如`tween`、`spring`等),它定义了单次动画的行为。
+- animation应使用类似于:`animation = tween(1000)` 的参数
+
+当一个动画规范被 infiniteRepeatable 包装后,它会持续不断地重复其定义的序列,直到 Composable 被移除。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 创建一个持续旋转的加载指示器动画。
+2. 实现一个不断闪烁的光标或提示图标。
+3. 为背景添加一个无限循环的、平滑过渡的颜色渐变或脉冲效果。
diff --git a/Animora/app/src/main/assets/markdown/keyframes.md b/Animora/app/src/main/assets/markdown/keyframes.md
new file mode 100644
index 0000000000..b81400f995
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/keyframes.md
@@ -0,0 +1,22 @@
+### 是什么?
+
+keyframes是一种基于时间的动画规范 (AnimationSpec) API。
+- `keyframes`动画规范允许开发者将动画过程精确地分解为多个时间点(关键帧),并在每个关键帧处指定动画属性的目标值。
+- `withKeyframesSpline`通过引入“样条曲线”(Spline)的概念,提供了比传统`keyframes`更平滑、更自然的插值方式。
+- 这意味着动画在关键帧之间不再是简单的线性过渡,而是可以根据贝塞尔曲线等定义的速度曲线进行平滑过渡,从而实现复杂的多步骤动画序列。
+- 它为构建高度定制化、富有层次感的动画提供了强大的工具,尤其适合那些需要精细控制每个动画阶段节奏的场景。
+
+这个 API 接受2个核心输入:timestamps, values。
+timestamps, values表示动画过程中的一系列特定时间点和在这些时间点上动画属性应达到的目标值。
+- timestamps应使用类似于:在 `keyframes` 代码块中定义 `100 at 0` 的参数
+- values应使用类似于:在 `keyframes` 代码块中定义 `200 at 100` 的参数
+
+当定义了这些时间点和目标值时,keyframes 会创建一个动画,该动画在指定的时间点精确地达到指定的值,并在这些点之间进行平滑插值。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 创建一个复杂的、多阶段的动画序列,例如一个物体先放大,再旋转,最后移动到别处。
+2. 实现一个“抖动”或“果冻”效果,通过在短时间内定义多个微小的位置变化关键帧。
+3. 制作一个非线性的加载动画,例如进度条在某些阶段快,在另一些阶段慢。
diff --git a/Animora/app/src/main/assets/markdown/shareElement.md b/Animora/app/src/main/assets/markdown/shareElement.md
new file mode 100644
index 0000000000..5efd6a0183
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/shareElement.md
@@ -0,0 +1,24 @@
+### 是什么?
+
+sharedElement 是一种**状态驱动**的**共享元素过渡动画** API。
+- 用于在不同 Composable 状态之间建立“同一元素”的视觉连续性
+- 通过共享 key,将新旧 Composable 绑定为同一个视觉元素
+- 在状态切换时自动计算位置、尺寸与裁剪差异并生成过渡动画
+
+这个 API 接受 2 个核心输入:sharedContentState、animatedVisibilityScope。
+
+- sharedContentState 应使用类似于:rememberSharedContentState(key: Any) 的参数,其中 key 需要在状态切换前后保持稳定
+- animatedVisibilityScope 应使用类似于:this@AnimatedContent 或 this@AnimatedVisibility 的作用域引用
+
+当 sharedContentState 对应的元素在 animatedVisibilityScope 控制的状态发生变化时,
+sharedElement 会在 SharedTransitionLayout 提供的环境中,
+自动匹配新旧 Composable,
+并对该元素执行平滑的跨布局共享过渡动画。
+
+### 适用于:
+
+这个 API 可以适用于以下情况:
+
+1. 列表页与详情页之间的图片、头像等视觉焦点的共享过渡
+2. 同一元素在不同布局容器之间切换(如 Row 与 Column)
+3. 基于状态变化,对同一 UI 元素进行尺寸、位置或层级强调的动画表现
\ No newline at end of file
diff --git a/Animora/app/src/main/assets/markdown/shareTransition.md b/Animora/app/src/main/assets/markdown/shareTransition.md
new file mode 100644
index 0000000000..2927eab1b0
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/shareTransition.md
@@ -0,0 +1,21 @@
+### 是什么?
+
+shareTransition是一种状态驱动的布局过渡动画 API。
+- shareTransition是Compose中用于实现跨Composable之间共享视觉元素的平滑过渡动画的核心机制。
+- 它通过SharedTransitionLayout作为布局容器,并配合Modifier.sharedElement修饰符,智能地识别并连接不同布局状态下具有相同视觉概念的元素。
+- 当布局发生变化时,shareTransition会自动计算这些共享元素在起始和结束状态之间的位置、大小、形状等差异,并生成一系列精美的、如魔术般连贯的动画,使得元素仿佛从一个位置“飞”到另一个位置并自然变形。
+- 这极大地增强了用户在应用中导航时的体验,帮助他们直观地理解元素在不同屏幕或状态间的变换过程,从而提升了整个应用的视觉连贯性和用户满意度。
+
+这个 API 接受1个核心输入:共享元素标识符。
+共享元素标识符表示在不同布局中逻辑上相同的视觉元素。
+- 共享元素标识符应使用类似于:`rememberSharedContentState(key = "...")` 的参数
+
+当共享元素标识符改变时,shareTransition 会自动计算这些共享元素在起始和结束状态之间的位置、大小、形状等差异,并生成一系列精美的、如魔术般连贯的动画,使得元素仿佛从一个位置“飞”到另一个位置并自然变形。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 在列表视图和详情视图之间共享图片和标题,创造无缝的导航体验。
+2. 在两个不同的屏幕或Composable之间平滑过渡一个封面图片或卡片。
+3. 为相册应用中的缩略图到全屏大图的切换提供连贯的动画效果。
diff --git a/Animora/app/src/main/assets/markdown/snap.md b/Animora/app/src/main/assets/markdown/snap.md
new file mode 100644
index 0000000000..46002e9c25
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/snap.md
@@ -0,0 +1,20 @@
+### 是什么?
+
+snap是一种即时切换动画规范 (AnimationSpec) API。
+- `snap`是Compose动画中一个独特的`AnimationSpec`,它的核心功能是实现值的瞬间变化,而**不伴随任何过渡动画**。
+- 当您使用`snap`作为动画规范时,目标值会立即跳变到最终状态,动画持续时间为0。
+- 这与平滑过渡的动画形成鲜明对比,适用于那些需要即时更新UI状态,不希望有任何视觉延迟或动画效果的场景。
+- 例如,当用户快速切换选项卡、重置某个状态或在某些条件下需要强制UI立即呈现新状态时,`snap`是理想的选择。
+- 它提供了一种明确的方式来绕过动画,确保界面在特定情况下能够快速、直接地响应用户操作,避免不必要的动画开销。
+
+这个 API 接受0个核心输入:。
+
+当使用 snap 规范时,动画目标值会立即跳变到最终状态,动画持续时间为0。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 当需要立即重置UI元素的状态或位置时,例如点击“重置”按钮。
+2. 在快速切换标签页或视图时,避免不必要的过渡动画,确保即时响应。
+3. 在某些性能敏感的场景下,禁用动画以减少计算开销。
diff --git a/Animora/app/src/main/assets/markdown/spring.md b/Animora/app/src/main/assets/markdown/spring.md
new file mode 100644
index 0000000000..46f8d28062
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/spring.md
@@ -0,0 +1,22 @@
+### 是什么?
+
+spring是一种基于物理的动画规范 (AnimationSpec) API。
+- `spring`动画本身是Compose中模拟物理弹簧行为的`AnimationSpec`。
+- 它能够创建出具有真实物理反馈的弹簧动画,通过调整阻尼比(dampingRatio)和刚度(stiffness)等参数,精确控制动画的回弹力度和速度。
+- `withMediumSpringSpec`特指一种提供中等弹性和回弹效果的配置,它使得UI元素的运动更加自然、生动,避免了线性动画的机械感。
+- 这种物理特性让用户感觉界面富有“生命力”,每次交互都伴随着流畅且自然的反馈,从而显著提升了用户体验的趣味性和应用的整体质感。
+
+这个 API 接受2个核心输入:dampingRatio, stiffness。
+dampingRatio, stiffness表示阻尼比和刚度,分别用于控制振荡的衰减速度和弹簧的“硬度”。
+- dampingRatio应使用类似于:`dampingRatio = Spring.DampingRatioMediumBouncy` 的参数
+- stiffness应使用类似于:`stiffness = Spring.StiffnessLow` 的参数
+
+当这两个参数改变时,spring 会改变动画的物理表现,从而创造出从非常有弹性到几乎没有弹性的各种动画效果。
+
+· 适用于:
+
+这个API可以适用于一下情况
+
+1. 为元素的出现或消失添加一个富有弹性的缩放效果。
+2. 在用户拖拽一个项目并释放后,使其以弹簧效果回到原位。
+3. 模拟物理按压反馈,当用户点击按钮时,使其有轻微下沉和回弹的动画。
diff --git a/Animora/app/src/main/assets/markdown/springSpec.md b/Animora/app/src/main/assets/markdown/springSpec.md
new file mode 100644
index 0000000000..4f4b8e5a3f
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/springSpec.md
@@ -0,0 +1,27 @@
+### 是什么?
+
+SpringSpec是一种状态驱动的Spring(弹簧)动画 API。
+
+- 基于物理弹簧模型描述动画行为
+- 动画由状态变化触发,而非显式时间控制
+- 通过阻尼与刚度参数决定动画趋近稳定的方式
+
+这个API接受2个核心输入:dampingRatio、stiffness。
+
+ampingRatio 表示弹簧系统的阻尼比例,用于控制动画是否振荡以及振荡幅度。
+- dampingRatio应使用类似于:Spring.DampingRatioMediumBouncy 的实现方式,而不是直接使用魔法数
+
+stiffness 表示弹簧的刚度系数,用于控制动画变化的速度快慢。
+- stiffness 应使用类似于:Spring.StiffnessMedium 的实现方式,而不是直接使用魔法数
+
+当dampingRatio或stiffness改变时, SpringSpec会重新计算弹簧模型,并基于新的物理参数驱动动画向目标状态收敛。
+
+---
+
+### 适用于:
+
+这个 API 可以适用于以下情况
+
+1. 需要自然回弹效果的 UI 状态切换
+2. 动画可能被频繁打断并重新触发的场景
+3. 不关心固定时长,而关心动画稳定状态的过渡效果
\ No newline at end of file
diff --git a/Animora/app/src/main/assets/markdown/updateTransition.md b/Animora/app/src/main/assets/markdown/updateTransition.md
new file mode 100644
index 0000000000..20614ae53a
--- /dev/null
+++ b/Animora/app/src/main/assets/markdown/updateTransition.md
@@ -0,0 +1,21 @@
+### 是什么?
+
+updateTransition是一种状态驱动的多属性同步动画控制器 API。
+- updateTransition是Jetpack Compose中处理复杂动画场景的关键API,它允许您基于一个单一的、可观测的状态来协调和驱动多个不同的动画属性同步执行。
+- 不同于单值动画API只关注一个属性,updateTransition通过创建一个`Transition`对象,并将多个子动画(如颜色、大小、透明度等)绑定到这个共享的`Transition`实例上。
+- 当基础状态发生变化时,所有关联的子动画都会同步地开始、进行和结束,从而保证了视觉上的一致性和连贯性。
+- 这使得开发者能够轻松构建出复杂的、多维度的动画效果,如一个组件在展开时同时改变颜色、大小和透明度,极大地提升了用户界面的表现力和交互质量。
+
+这个 API 接受1个核心输入:targetState。
+targetState表示一个可观测的状态,用于驱动多个不同的动画属性同步执行。
+- targetState应使用类似于:`val currentState by remember { mutableStateOf(BoxState.Collapsed) }` 的参数
+
+当targetState改变时,updateTransition 会让所有关联的子动画(如颜色、大小、透明度等)都会同步地开始、进行和结束,从而保证了视觉上的一致性和连贯性。
+
+### 适用于:
+
+这个API可以适用于一下情况
+
+1. 当一个组件展开时,同时改变其背景颜色、尺寸和圆角大小。
+2. 实现一个复杂的开关按钮,其滑块、背景和图标都具有同步的动画效果。
+3. 根据一个枚举状态(如加载中、成功、失败),同步更新多个UI元素的颜色、透明度和位置。
diff --git a/Animora/app/src/main/ic_launcher-playstore.png b/Animora/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000000..4e37792238
Binary files /dev/null and b/Animora/app/src/main/ic_launcher-playstore.png differ
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/.gitignore b/Animora/app/src/main/java/com/baidaidai/animora/.gitignore
new file mode 100644
index 0000000000..cde952be38
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/.gitignore
@@ -0,0 +1 @@
+TODO.md
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/FS_Explain.md b/Animora/app/src/main/java/com/baidaidai/animora/FS_Explain.md
new file mode 100644
index 0000000000..a28ee8d40f
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/FS_Explain.md
@@ -0,0 +1,116 @@
+# Animora 项目目录结构说明
+
+本文档旨在帮助开发者深入理解 `Animora` 项目中每个文件和目录的用途。
+
+---
+
+## 根目录
+
+此目录是应用主要源码的根目录。
+
+- `InfoActivity.kt`: “关于”页面的 `Activity`,是该独立页面的入口点。
+- `MainActivity.kt`: 应用的主 `Activity`,承载所有主要的 Jetpack Compose UI 界面。
+- `TODO.md`: 项目的待办事项列表,用于追踪开发任务和未来计划。
+- `FS_Explain.md`: (本文档) 项目文件系统说明。
+
+---
+
+## `components/`
+
+此目录存放应用中所有的 Jetpack Compose UI 组件,并按功能和屏幕进行组织。
+
+### `components/animation/`
+
+存放与动画效果展示相关的组件。
+
+- **`demo/`**: 包含各种独立、可复现的动画效果示例。
+ - `animatable.kt`: 展示如何使用 `Animatable` API 创建更灵活的动画。
+ - `infiniteTransition.kt`: 展示如何使用 `rememberInfiniteTransition` 创建无限循环的动画。
+ - **`singel/`**: 存放单值动画或简单动画的示例。
+ - `animateColorAsState.kt`: `animateColorAsState` 的用法示例,用于平滑的颜色过渡。
+ - `animateContentSize.kt`: `animateContentSize` 的用法示例,用于使组件尺寸变化时产生动画效果。
+ - `animateContentVisibility.kt`: 内容可见性动画示例,可能指 `AnimatedVisibility`。
+ - `animateOpacityAsState.kt`: 透明度动画示例,可能使用 `animateFloatAsState` 实现。
+ - `shareElement.kt`: 共享元素 (Shared Element) 转场动画的增强版示例。
+ - `shareTransition.kt`: 共享元素转场动画的示例。
+ - `updateTransition.kt`: `updateTransition` 的用法示例,用于管理多个值的状态转换动画。
+
+- **`detail/`**: 动画详情页面的组件。
+ - `index.kt`: 动画详情页面的主 Composable 函数。
+ - `NecessaryComponents.kt`: 详情页面所需的辅助或通用组件。
+
+### `components/info/`
+
+“关于”页面的 UI 组件。
+
+- `authorArea.kt`: 显示作者信息的 Composable 组件。
+- `index.kt`: “关于”页面的主 Composable 函数。
+- `infoArea.kt`: 显示应用信息的 Composable 组件。
+- **`infoScreen/`**:
+ - `NecessaryComponents.kt`: “关于”屏幕所需的辅助或通用组件。
+
+### `components/spring/`
+
+新增的 Spring 动画工作室,提供与 `spring` 动画交互的功能。
+
+- `index.kt`: Spring 动画工作室页面的主 Composable 函数。
+- **`animationStudio/`**:
+ - `index.kt`: 互动式动画工作室的主界面 UI。
+- **`components/`**:
+ - `NecessaryComponents.kt`: Spring 工作室所需的辅助或通用组件。
+ - `buttonGroups.kt`: 用于控制动画参数的按钮组。
+ - `springSpecStudioController.kt`: Spring 动画规格(如刚度、阻尼比)的交互式控制器。
+- **`model/`**:
+ - `index.kt`: Spring 工作室的数据模型。
+ - **`list/`**:
+ - `index.kt`: 定义 Spring 动画示例列表的数据。
+
+### `components/StartScreen/`
+
+应用启动后的主屏幕界面,包含底部导航和各个标签页。
+
+- `index.kt`: 主屏幕的入口 Composable,可能包含底部导航栏的实现逻辑。
+
+- **`home/`**: 主页标签页的组件。
+ - `index.kt`: 主页界面的主 Composable 函数。
+ - `introduceCard.kt`: 主页上用于功能介绍或欢迎语的卡片组件。
+ - `onlySpringSpec.kt`: 一个专门用于演示 `spring` 动画规格的组件或示例。
+
+- **`list/`**: 动画列表标签页的组件。
+ - `index.kt`: 动画列表界面的主 Composable 函数。
+ - **`components/`**: 列表页的子组件。
+ - `animationListItem.kt`: 列表中单个动画项的 Composable 组件。
+ - `Header.kt`: 列表页面的通用页头组件。
+ - `modalBottomSheet.kt`: 列表页面中使用的模态底部工作表 (Modal Bottom Sheet) 组件。
+ - `principledHeader.kt`: 一个遵循特定设计原则的特殊页头组件。
+ - **`model/`**:
+ - `animationList.kt`: 定义动画列表所需的数据模型或提供静态数据。
+
+- **`model/`**:
+ - `barItemInside.kt`: 定义底部导航栏项目的数据模型或 Composable。
+ - `homeScreenBlurViewModel.kt`: 用于管理主屏幕背景模糊效果状态的 `ViewModel`。
+
+---
+
+## `shared/`
+
+存放跨多个组件或模块共享的代码,以提高代码复用性。
+
+- **`components/`**:
+ - `NecessaryComponents.kt`: 存放整个应用范围内可复用的通用 UI 组件。
+- **`dataClass/`**:
+ - `AnimationDatas.kt`: 定义应用核心的数据类,如此处是动画相关的数据结构。
+- **`viewModel/`**:
+ - `animationDatasViewModel.kt`: 管理和提供动画数据的 `ViewModel`,处理业务逻辑并与 UI 分离。
+ - `blueStateViewModel.kt`: 一个特定的 `ViewModel`,用于管理某个蓝色主题或状态相关的逻辑。
+
+---
+
+## `ui/`
+
+标准的 Android UI 目录,用于定义应用视觉风格。
+
+- **`theme/`**:
+ - `Color.kt`: 定义应用的所有颜色值,形成调色板。
+ - `Theme.kt`: 定义应用的整体 Compose 主题,包括颜色、排版和形状。
+ - `Type.kt`: 定义应用的排版样式,如 `H1`, `Body1` 等字体风格。
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/InfoActivity.kt b/Animora/app/src/main/java/com/baidaidai/animora/InfoActivity.kt
new file mode 100644
index 0000000000..01714663a8
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/InfoActivity.kt
@@ -0,0 +1,28 @@
+package com.baidaidai.animora
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import com.baidaidai.animora.components.info.infoScreen
+import com.baidaidai.animora.ui.theme.TestAppTheme
+
+class InfoActivity : ComponentActivity() {
+ @OptIn(
+ ExperimentalMaterial3Api::class,
+ ExperimentalMaterial3ExpressiveApi::class
+ )
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ TestAppTheme {
+ infoScreen()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/MainActivity.kt b/Animora/app/src/main/java/com/baidaidai/animora/MainActivity.kt
new file mode 100644
index 0000000000..66d205eda9
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/MainActivity.kt
@@ -0,0 +1,218 @@
+package com.baidaidai.animora
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionLayout
+import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import com.baidaidai.animora.ui.theme.TestAppTheme
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.baidaidai.animora.components.animation.detail.animationDetailContainer
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.baidaidai.animora.shared.viewModel.animationDatasViewModel
+import androidx.compose.ui.platform.LocalContext
+import com.baidaidai.animora.components.spring.springSpecSceenContainer
+import com.baidaidai.animora.components.StartScreen.startScreenContainer
+import com.baidaidai.animora.shared.viewModel.blueStateViewModel
+
+/**
+ * 提供当前动画数据的 [animationDatasViewModel]。
+ *
+ * 该 [CompositionLocal] 用于在 Compose 层级中共享动画相关状态,
+ * 其生命周期应与提供它的上层 Composable 保持一致。
+ *
+ * 此 Local 不会自行创建或管理 [animationDatasViewModel]。
+ * 如果在未通过 [CompositionLocalProvider] 提供的情况下访问,
+ * 将在运行时抛出异常。
+ *
+ * 使用示例:
+ *
+ * ```kotlin
+ * CompositionLocalProvider(
+ * LocalAnimationViewModel provides animationDatasViewModel
+ * ) {
+ * // 使用 LocalAnimationViewModel.current
+ * }
+ * ```
+ *
+ * @see animationDatasViewModel
+ * @see CompositionLocalProvider
+ */
+val LocalAnimationViewModel = compositionLocalOf {
+ error("No animationDatasViewModel provided")
+}
+
+/**
+ * 提供当前 [SharedTransitionScope] 的 [CompositionLocal]。
+ *
+ * 如果在未提供的情况下访问该值,将在运行时抛出异常。
+ * 请确保在使用前已通过 [CompositionLocalProvider] 正确注入。
+ *
+ * 使用示例:
+ *
+ * ```kotlin
+ * CompositionLocalProvider(
+ * LocalSharedTransitionScope provides this@SharedTransitionLayout
+ * ) {
+ * // 可以安全地使用 LocalSharedTransitionScope.current
+ * }
+ * ```
+ *
+ * @see SharedTransitionScope
+ * @see CompositionLocalProvider
+ */
+@OptIn(ExperimentalSharedTransitionApi::class)
+val LocalSharedTransitionScope = compositionLocalOf{
+ error("No SharedTransitionScope provided")
+}
+
+/**
+ * 提供当前 [AnimatedContentScope] 的 [CompositionLocal]。
+ *
+ * 如果在未提供的情况下访问该值,将在运行时抛出异常。
+ * 请确保在使用前已通过 [CompositionLocalProvider] 正确注入。
+ *
+ * 使用示例:
+ *
+ * ```kotlin
+ * CompositionLocalProvider(
+ * LocalAnimatedContentScope provides animatedContentScope
+ * ) {
+ * // 可以安全地使用 LocalAnimatedContentScope.current
+ * }
+ * ```
+ *
+ * @see AnimatedContentScope
+ * @see CompositionLocalProvider
+ */
+@OptIn(ExperimentalSharedTransitionApi::class)
+val LocalAnimatedContentScope = compositionLocalOf{
+ error("No AnimatedContentScope provided")
+}
+
+class MainActivity : ComponentActivity() {
+ @OptIn(
+ ExperimentalMaterial3Api::class,
+ ExperimentalMaterial3ExpressiveApi::class
+ )
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ TestAppTheme {
+ val animationDetailsViewModel = viewModel()
+ CompositionLocalProvider(
+ LocalAnimationViewModel provides animationDetailsViewModel
+ ) {
+ AnimoraApp()
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Animora 应用的根 Composable。
+ *
+ * ## 该函数负责:
+ * - 初始化应用级的导航结构([NavHost])
+ * - 提供跨页面共享的 [SharedTransitionLayout] 上下文
+ * - 在导航目的地作用域内注入动画相关的 [CompositionLocal]
+ *
+ * [AnimoraApp] 本身不包含具体界面实现,
+ * 而是作为应用的结构性入口,协调导航、动画作用域与状态注入。
+ *
+ * ## 架构说明
+ * - 每个导航目的地在进入时都会被包裹在同一个
+ * [SharedTransitionLayout] 中,以支持跨页面的共享过渡动画
+ * - [LocalSharedTransitionScope] 与 [LocalAnimatedContentScope]
+ * 仅在对应的 `composable` 作用域内提供
+ * - 应用级的状态(如 [blueStateViewModel])在此层级创建,
+ * 以确保在导航切换过程中保持稳定
+ *
+ * ⚠️ 该 Composable 假定其子级在使用动画相关 Local 时,
+ * 已正确运行在对应的过渡与内容作用域中。
+ *
+ * @see NavHost
+ * @see SharedTransitionLayout
+ * @see LocalSharedTransitionScope
+ * @see LocalAnimatedContentScope
+ */
+@ExperimentalMaterial3ExpressiveApi
+@OptIn(
+ ExperimentalMaterial3Api::class,
+ ExperimentalFoundationApi::class,
+ ExperimentalSharedTransitionApi::class
+)
+@Composable
+fun AnimoraApp(
+) {
+
+ val homeViewNavController = rememberNavController()
+ val totalNavigationController = rememberNavController()
+ val blueStateViewModel = viewModel()
+ val context = LocalContext.current
+
+ SharedTransitionLayout {
+ NavHost(
+ navController = totalNavigationController,
+ startDestination = "Start"
+ ) {
+ composable(
+ route = "Start"
+ ){
+ CompositionLocalProvider(
+ LocalSharedTransitionScope provides this@SharedTransitionLayout,
+ LocalAnimatedContentScope provides this@composable
+ ) {
+ startScreenContainer(
+ context = context,
+ homeViewNavController = homeViewNavController,
+ totalNavigationController = totalNavigationController,
+ )
+ }
+ }
+ composable(
+ route = "Detail"
+ ){
+ CompositionLocalProvider(
+ LocalSharedTransitionScope provides this@SharedTransitionLayout,
+ LocalAnimatedContentScope provides this@composable
+ ) {
+ animationDetailContainer(
+ blueStateViewModel = blueStateViewModel,
+ navController = totalNavigationController,
+ )
+ }
+ }
+ composable(
+ route = "springStudio"
+ ){
+ springSpecSceenContainer(
+ navController = totalNavigationController
+ )
+ }
+ }
+ }
+}
+
+
+
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@PreviewLightDark
+@Composable
+private fun TextBoxPreview() {
+ TestAppTheme {
+ AnimoraApp()
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/components/NecessaryComponents.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/components/NecessaryComponents.kt
new file mode 100644
index 0000000000..18f5136832
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/components/NecessaryComponents.kt
@@ -0,0 +1,90 @@
+package com.baidaidai.animora.components.StartScreen.components
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.List
+import androidx.compose.material.icons.outlined.Home
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.res.painterResource
+import androidx.navigation.NavHostController
+import com.baidaidai.animora.R
+import com.baidaidai.animora.components.StartScreen.model.BarItemInside
+import kotlinx.coroutines.launch
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.navigation.compose.currentBackStackEntryAsState
+
+final object NecessaryComponents {
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun homeTopAppBar(
+ onClick: () -> Unit
+ ){
+ TopAppBar(
+ title = {
+ Text("Animora")
+ },
+ actions = {
+ IconButton(
+ onClick = onClick
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.info_24px),
+ contentDescription = "Info"
+ )
+ }
+ },
+ modifier = Modifier
+ .padding(horizontal = 5.dp)
+ )
+ }
+
+ @Composable
+ fun homeButtomBar(
+ controller: NavHostController
+ ){
+ val coroutineScope = rememberCoroutineScope()
+
+ NavigationBar {
+
+ val NavigationRenderingList = listOf(
+ BarItemInside(0, Icons.Outlined.Home, "Home"),
+ BarItemInside(1, Icons.AutoMirrored.Outlined.List, "List")
+ )
+
+ var selected by rememberSaveable { mutableIntStateOf(0) }
+ val navBackStackEntry by controller.currentBackStackEntryAsState()
+ val currentRoute = navBackStackEntry?.destination?.route
+
+ NavigationRenderingList.forEach { item ->
+ NavigationBarItem(
+ selected = item.contentDescription == currentRoute,
+ onClick = {
+ selected = item.number
+ coroutineScope.launch {
+ if (currentRoute == item.contentDescription) else controller.navigate(item.contentDescription)
+ }
+ },
+ icon = {
+ Icon(item.pattern, item.contentDescription)
+ },
+ label = {
+ Text(item.contentDescription)
+ }
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/index.kt
new file mode 100644
index 0000000000..85c4776ad7
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/index.kt
@@ -0,0 +1,37 @@
+package com.baidaidai.animora.components.StartScreen.home
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * A container composable for the home screen.
+ *
+ * This composable arranges the main content of the home screen, including the
+ * introduction card and other feature cards.
+ *
+ * @param contentPadding The padding values provided by the Scaffold to avoid overlapping with system bars.
+ * @param onlySpringSpecOnClick A lambda function to be invoked when the "Only For SpringSpec" card is clicked.
+ */
+@Composable
+fun homeScreenComtainer(
+ contentPadding: PaddingValues,
+ onlySpringSpecOnClick: ()-> Unit
+){
+ Column(
+ modifier = Modifier
+ .background(color = MaterialTheme.colorScheme.background)
+ .padding(contentPadding)
+ .padding(start = 20.dp, end = 20.dp)
+ ) {
+ introduceCard()
+ onlySpringSpce(
+ onClick = onlySpringSpecOnClick
+ )
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/introduceCard.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/introduceCard.kt
new file mode 100644
index 0000000000..6e7e4fa225
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/introduceCard.kt
@@ -0,0 +1,88 @@
+package com.baidaidai.animora.components.StartScreen.home
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardColors
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.R
+
+/**
+ * A composable that displays an introduction card for the author.
+ *
+ * This card contains the author's avatar, name, GitHub handle, and a short note.
+ * It is styled using Material Design components.
+ */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun introduceCard(){
+ Card(
+ colors = CardColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ disabledContainerColor = MaterialTheme.colorScheme.error,
+ disabledContentColor = MaterialTheme.colorScheme.onError
+ ),
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(15.dp)
+ ) {
+ Row {
+ Image(
+ painter = painterResource(R.drawable.createrbai),
+ contentDescription = "Avater",
+ modifier = Modifier
+ .size(100.dp)
+ )
+ Column(
+ modifier = Modifier
+ .height(100.dp),
+ verticalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = "Author: Creater. Bai",
+ style = MaterialTheme.typography.titleSmall
+ )
+ Text(
+ text= "Github: @Baidaidai-GFWD-origin",
+ style = MaterialTheme.typography.titleSmall
+ )
+ }
+ }
+ HorizontalDivider(
+ thickness = 1.dp,
+ color = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = Modifier
+ .padding(horizontal = 10.dp)
+ )
+ Column(
+ modifier = Modifier
+ .padding(top = 10.dp, start = 10.dp, end = 10.dp)
+ ){
+ Text(
+ text = stringResource(R.string.creater_note),
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/onlySpringSpec.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/onlySpringSpec.kt
new file mode 100644
index 0000000000..dde3c6796b
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/home/onlySpringSpec.kt
@@ -0,0 +1,67 @@
+package com.baidaidai.animora.components.StartScreen.home
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ArrowForwardIos
+import androidx.compose.material.icons.outlined.QuestionMark
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardColors
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * A composable that displays a clickable card to navigate to the "SpringSpec" feature.
+ *
+ * This card serves as a navigation entry point to a specific feature,
+ * indicated as "Only For SpringSpec". It includes an icon and text, and a forward arrow
+ * to suggest navigation.
+ *
+ * @param onClick A lambda function to be invoked when the card is clicked.
+ */
+@Composable
+fun onlySpringSpce(
+ onClick: ()-> Unit
+){
+ Card(
+ enabled = true,
+ onClick = onClick,
+ colors = CardColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ disabledContainerColor = MaterialTheme.colorScheme.error,
+ disabledContentColor = MaterialTheme.colorScheme.onError
+ ),
+ modifier = Modifier
+ .padding(top = 20.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(15.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Row {
+ Icon(Icons.Outlined.QuestionMark, contentDescription = "Question LOGO")
+ Text(
+ text = "Only For SpringSpec? (New)",
+ modifier = Modifier
+ .padding(start = 10.dp)
+ )
+ }
+ Icon(Icons.Outlined.ArrowForwardIos, contentDescription = "Go SpringSpec")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/index.kt
new file mode 100644
index 0000000000..be86516d61
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/index.kt
@@ -0,0 +1,90 @@
+package com.baidaidai.animora.components.StartScreen
+
+import android.content.Context
+import android.content.Intent
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.blur
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import com.baidaidai.animora.InfoActivity
+import com.baidaidai.animora.components.StartScreen.home.homeScreenComtainer
+import com.baidaidai.animora.components.StartScreen.list.animationListContainer
+import com.baidaidai.animora.components.StartScreen.model.homeScreenBlurViewModel
+import com.baidaidai.animora.components.StartScreen.components.NecessaryComponents
+
+/**
+ * The main container composable for the start screen, managing the overall layout and navigation.
+ *
+ * This composable sets up a [Scaffold] with a top app bar and a bottom navigation bar.
+ * It hosts a [NavHost] to switch between the "Home" and "List" screens.
+ * It also handles a blur effect that can be triggered by a [homeScreenBlurViewModel].
+ *
+ * @param context The Android [Context] used for creating intents.
+ * @param homeViewNavController The [NavHostController] for the inner navigation between Home and List screens.
+ * @param totalNavigationController The main [NavHostController] for navigating to other parts of the app.
+ */
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+fun startScreenContainer(
+ context: Context,
+ homeViewNavController: NavHostController,
+ totalNavigationController: NavHostController,
+){
+ val intent = Intent(context,InfoActivity::class.java)
+
+ val homeScreenBlurViewModel = viewModel()
+ val blurStatus by homeScreenBlurViewModel.blurStatus.collectAsState()
+ val blurValue by animateDpAsState(
+ targetValue = if (blurStatus) 10.dp else 0.dp
+ )
+
+ Scaffold(
+ topBar = {
+ NecessaryComponents.homeTopAppBar {
+ context.startActivity(intent)
+ }
+ },
+ bottomBar = {
+ NecessaryComponents.homeButtomBar(
+ controller = homeViewNavController
+ )
+ },
+ modifier = Modifier
+ .blur(blurValue)
+ ) { contentPadding ->
+ NavHost(
+ navController = homeViewNavController,
+ startDestination = "Home",
+ ) {
+ composable(
+ route = "Home"
+ ){
+ homeScreenComtainer(
+ contentPadding = contentPadding,
+ onlySpringSpecOnClick = {
+ totalNavigationController.navigate("springStudio")
+ }
+ )
+ }
+ composable (
+ route = "List"
+ ) {
+ animationListContainer(
+ contentPaddingValues = contentPadding,
+ navController = totalNavigationController,
+ viewModel = homeScreenBlurViewModel,
+ )
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/Header.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/Header.kt
new file mode 100644
index 0000000000..e05a0e2255
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/Header.kt
@@ -0,0 +1,38 @@
+package com.baidaidai.animora.components.StartScreen.list.components
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import okhttp3.internal.http2.Header
+
+@Composable
+fun Header(
+ content: String
+){
+ Box(
+ contentAlignment = Alignment.CenterStart,
+ modifier = Modifier
+ .height(30.dp)
+ ) {
+ HorizontalDivider(
+ color = MaterialTheme.colorScheme.secondaryContainer,
+ thickness = 30.dp,
+ modifier = Modifier
+ .fillMaxSize()
+ )
+ Text(
+ color = MaterialTheme.colorScheme.onPrimaryContainer,
+ text = content,
+ modifier = Modifier
+ .padding(start = 20.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/animationListItem.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/animationListItem.kt
new file mode 100644
index 0000000000..cc6ab7922e
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/animationListItem.kt
@@ -0,0 +1,68 @@
+package com.baidaidai.animora.components.StartScreen.list.components
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.foundation.clickable
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.QuestionMark
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import com.baidaidai.animora.LocalAnimatedContentScope
+import com.baidaidai.animora.LocalSharedTransitionScope
+import com.baidaidai.animora.shared.dataClass.AnimationDatas
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
+@Composable
+fun animationListItem(
+ animationList: AnimationDatas,
+ listOnClick:suspend()-> Unit,
+ questionOnClick:suspend()-> Unit,
+){
+ val sharedTransitionScope = LocalSharedTransitionScope.current
+ val animatedContentScope = LocalAnimatedContentScope.current
+ val coroutineScope = rememberCoroutineScope()
+ with(sharedTransitionScope){
+ ListItem(
+ headlineContent = {
+ Text(
+ text = animationList.name,
+ modifier = Modifier
+ .sharedBounds(
+ sharedContentState = rememberSharedContentState("AnimationTitle-${animationList.name}"),
+ animatedVisibilityScope = animatedContentScope
+ )
+ )
+ },
+ trailingContent = {
+ IconButton(
+ onClick = {
+ coroutineScope.launch {
+ questionOnClick()
+ }
+ }
+ ) {
+ Icon(Icons.Outlined.QuestionMark, contentDescription = "Question")
+ }
+ },
+ modifier = Modifier
+ .clip(MaterialTheme.shapes.medium)
+ .clickable(
+ onClick = {
+ coroutineScope.launch {
+ listOnClick()
+ }
+ }
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/modalBottomSheet.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/modalBottomSheet.kt
new file mode 100644
index 0000000000..6bcc0ac711
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/modalBottomSheet.kt
@@ -0,0 +1,49 @@
+package com.baidaidai.animora.components.StartScreen.list.components
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.SheetState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun modalBottomSheet(
+ onDismissRequest:suspend ()-> Unit,
+ modalBottomSheetState: SheetState, @StringRes
+ bottomSheetContent: Int
+){
+
+ val coroutineScope = rememberCoroutineScope()
+
+ ModalBottomSheet(
+ onDismissRequest = {
+ coroutineScope.launch {
+ onDismissRequest()
+ }
+ },
+ sheetState = modalBottomSheetState,
+// modifier = Modifier
+// .blur(1.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .defaultMinSize(minHeight = 200.dp)
+ .padding(20.dp)
+ ) {
+ Text(
+ text = stringResource(bottomSheetContent)
+ )
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/principledHeader.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/principledHeader.kt
new file mode 100644
index 0000000000..f6af73f50a
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/components/principledHeader.kt
@@ -0,0 +1,45 @@
+package com.baidaidai.animora.components.StartScreen.list.components
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.components.StartScreen.list.model.animationList
+import com.baidaidai.animora.shared.dataClass.AnimationDatas
+
+@Composable
+fun principledHeader(
+ animationListDatas: AnimationDatas
+){
+ when(animationListDatas.id){
+ 0 -> {
+ Header(content = "animate*AsState Family")
+ }
+ 4 -> {
+ Header(content = "SharedTransition Family")
+ }
+ 6 -> {
+ Header(content = "updateTransition Family")
+ }
+ 7 -> {
+ Header(content = "Animatable Family")
+ }
+ 13 -> {
+ Header(content = "InfiniteTransition Family")
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun _principleHeaderPreview(){
+ principledHeader(animationList[1])
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/index.kt
new file mode 100644
index 0000000000..3ff15a7f37
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/index.kt
@@ -0,0 +1,112 @@
+package com.baidaidai.animora.components.StartScreen.list
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.blur
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import com.baidaidai.animora.LocalAnimationViewModel
+import com.baidaidai.animora.components.StartScreen.list.components.animationListItem
+import com.baidaidai.animora.components.StartScreen.list.components.modalBottomSheet
+import com.baidaidai.animora.components.StartScreen.list.components.principledHeader
+import com.baidaidai.animora.components.StartScreen.list.model.animationList
+import com.baidaidai.animora.components.StartScreen.model.homeScreenBlurViewModel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
+@Composable
+fun animationListContainer(
+ contentPaddingValues: PaddingValues,
+ navController: NavController,
+ viewModel: homeScreenBlurViewModel,
+){
+
+ val animationDatasViewModel = LocalAnimationViewModel.current
+ var bottomSheetState by rememberSaveable { mutableStateOf(false) }
+ val modalBottomSheetState = rememberModalBottomSheetState()
+ val coroutineScope = rememberCoroutineScope()
+ val homeScreenBlurViewModel = viewModel
+
+ @StringRes
+ var bottomSheetContent: Int by rememberSaveable { mutableIntStateOf(0) }
+
+ if (bottomSheetState){
+ modalBottomSheet(
+ onDismissRequest = {
+ modalBottomSheetState.hide()
+ bottomSheetState = !bottomSheetState
+ homeScreenBlurViewModel.changeBlurStatus(bottomSheetState)
+ },
+ modalBottomSheetState = modalBottomSheetState,
+ bottomSheetContent = bottomSheetContent
+ )
+ }
+ Column(
+ modifier = Modifier
+ .background(color = MaterialTheme.colorScheme.background)
+ .padding(contentPaddingValues)
+ ) {
+ LazyColumn {
+ items(
+ items = animationList,
+ key = { animationList ->
+ animationList.id
+ }
+ ) { animationList->
+ principledHeader(animationList)
+ Column(
+ modifier = Modifier
+ .padding(start = 20.dp, end = 20.dp)
+ ) {
+ animationListItem(
+ animationList = animationList,
+ listOnClick = {
+ animationDatasViewModel.changeSelectedAnimation(
+ animationDatas = animationList
+ )
+ navController.navigate("Detail")
+ },
+ questionOnClick = {
+ bottomSheetContent = animationList.shortInfo
+ coroutineScope.launch {
+ bottomSheetState = !bottomSheetState
+ homeScreenBlurViewModel.changeBlurStatus(bottomSheetState)
+ delay(500)
+ modalBottomSheetState.show()
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/model/animationList.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/model/animationList.kt
new file mode 100644
index 0000000000..0b2578a666
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/list/model/animationList.kt
@@ -0,0 +1,116 @@
+package com.baidaidai.animora.components.StartScreen.list.model
+
+import com.baidaidai.animora.components.animation.demo.singel.*
+import com.baidaidai.animora.R
+import com.baidaidai.animora.components.animation.demo.animatable
+import com.baidaidai.animora.components.animation.demo.infiniteTransition
+import com.baidaidai.animora.shared.dataClass.AnimationDatas
+
+
+val animationList = listOf(
+ AnimationDatas(
+ id = 0,
+ name = "AnimateColorAsState",
+ shortInfo = R.string.animateColorAsState_shortInfo,
+ details = "markdown/animateColorAsState.md",
+ animationFiles = { blueState -> animateColorAsState(blueState) }
+ ),
+ AnimationDatas(
+ id = 1,
+ name = "AnimateContentSize",
+ shortInfo = R.string.animateContentSize_shortInfo,
+ details = "markdown/animateContentSize.md",
+ animationFiles = { blueState -> animateContentSize(blueState) }
+ ),
+ AnimationDatas(
+ id = 2,
+ name = "AnimateContentVisibility",
+ shortInfo = R.string.animateContentVisibility_shortInfo,
+ details = "markdown/animateContentVisibility.md",
+ animationFiles = { blueState -> animateContentVisibility(blueState) }
+ ),
+ AnimationDatas(
+ id = 3,
+ name = "AnimateOpacityAsState",
+ shortInfo = R.string.animateOpacityAsState_shortInfo,
+ details = "markdown/animateOpacityAsState.md",
+ animationFiles = { blueState -> animateOpacityAsState(blueState) }
+ ),
+ AnimationDatas(
+ id = 4,
+ name = "shareBorder",
+ shortInfo = R.string.shareBorder_shortInfo,
+ details = "markdown/shareTransition.md",
+ animationFiles = { blueState -> shareTransition(blueState) }
+ ),
+ AnimationDatas(
+ id = 5,
+ name = "shareElement",
+ shortInfo = R.string.shareElement_shortInfo,
+ details = "markdown/shareElement.md",
+ animationFiles = { blueState -> sharedElement(blueState) }
+ ),
+ AnimationDatas(
+ id = 6,
+ name = "UpdateTransition",
+ shortInfo = R.string.updateTransition_shortInfo,
+ details = "markdown/updateTransition.md",
+ animationFiles = { blueState -> _updateTransition(blueState) }
+ ),
+ AnimationDatas(
+ id = 7,
+ name = "Animatable",
+ shortInfo = R.string.AnimateTo_shortInfo,
+ details = "markdown/animatable.md",
+ animationFiles = { blueState -> animatable.AnimateTo(blueState) }
+ ),
+ AnimationDatas(
+ id = 8,
+ name = "WithMediumSpringSpec",
+ shortInfo = R.string.withMediumSpringSpec_shortInfo,
+ details = "markdown/spring.md",
+ animationFiles = { blueState -> animatable.withMediumSpringSpec(blueState) }
+ ),
+ AnimationDatas(
+ id = 9,
+ name = "WithDIYBezier",
+ shortInfo = R.string.withDIYBezier_shortInfo,
+ details = "markdown/CubicBezierEasing.md",
+ animationFiles = { blueState -> animatable.withDIYBezier(blueState) }
+ ),
+ AnimationDatas(
+ id = 10,
+ name = "WithKeyframesSpline",
+ shortInfo = R.string.withKeyframesSpline_shortInfo,
+ details = "markdown/keyframes.md",
+ animationFiles = { blueState -> animatable.withKeyframesSpline(blueState) }
+ ),
+ AnimationDatas(
+ id = 11,
+ name = "WithInfinityRepeatable",
+ shortInfo = R.string.withInfinityRepeatable_shortInfo,
+ details = "markdown/infiniteRepeatable.md",
+ animationFiles = { blueState -> animatable.withInfinityRepeatable(blueState) }
+ ),
+ AnimationDatas(
+ id = 12,
+ name = "WithSnap",
+ shortInfo = R.string.withSnap_shortInfo,
+ details = "markdown/snap.md",
+ animationFiles = { blueState -> animatable.withSnap(blueState) }
+ ),
+ AnimationDatas(
+ id = 13,
+ name = "AnimateColor",
+ shortInfo = R.string.animateColor_shortInfo,
+ details = "markdown/animateColor.md",
+ animationFiles = { infiniteTransition.animateColor() }
+ ),
+ AnimationDatas(
+ id = 14,
+ name = "AnimateFloat",
+ shortInfo = R.string.animateFloat_shortInfo,
+ details = "markdown/animateFloat.md",
+ animationFiles = { infiniteTransition.animateFloat() }
+ )
+)
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/model/barItemInside.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/model/barItemInside.kt
new file mode 100644
index 0000000000..b6d9c5740b
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/model/barItemInside.kt
@@ -0,0 +1,9 @@
+package com.baidaidai.animora.components.StartScreen.model
+
+import androidx.compose.ui.graphics.vector.ImageVector
+
+data class BarItemInside(
+ val number: Int,
+ val pattern: ImageVector,
+ val contentDescription: String
+)
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/model/homeScreenBlurViewModel.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/model/homeScreenBlurViewModel.kt
new file mode 100644
index 0000000000..ecb8782465
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/StartScreen/model/homeScreenBlurViewModel.kt
@@ -0,0 +1,17 @@
+package com.baidaidai.animora.components.StartScreen.model
+
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class homeScreenBlurViewModel: ViewModel() {
+ private var _blurStatus = MutableStateFlow(false)
+ val blurStatus = _blurStatus.asStateFlow()
+
+ fun changeBlurStatus(value: Boolean){
+ _blurStatus.update {
+ value
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/FS_Explain.md b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/FS_Explain.md
new file mode 100644
index 0000000000..8f9d9a7d81
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/FS_Explain.md
@@ -0,0 +1,80 @@
+# `demo` 文件夹分析
+
+本文档对 `components/animation/demo` 目录下的文件进行分析和总结。
+
+---
+
+### 目录结构
+
+```
+demo/
+├── FS_Explain.md
+├── animatable.kt
+├── infiniteTransition.kt
+└── singel/
+```
+
+- `FS_Explain.md`: (本文档) 对 `demo` 文件夹的说明。
+- `animatable.kt`: 包含使用 `Animatable` API 实现的各种动画效果的示例代码。
+- `infiniteTransition.kt`: 包含使用 `rememberInfiniteTransition` API 实现无限循环动画的示例代码。
+- `singel` 文件夹包含了更高阶、更易用的 Jetpack Compose 动画 API 示例。
+---
+
+### 文件内容总结
+
+#### 1. `animatable.kt`
+
+该文件通过一个名为 `animatable` 的单例对象,提供了多个独立的 Jetpack Compose `@Composable` 函数,用于演示 `Animatable` 的强大功能。`Animatable` 允许对单个值进行动画处理,并能在协程中通过 `animateTo` 方法精确控制动画的启动、停止和规格。
+
+**主要演示内容包括:**
+
+- `AnimateTo`: 基础用法,演示如何根据状态变化,将一个浮点型 `alphaValue` (透明度) 从一个值动画到另一个值。
+- `withMediumSpringSpec`: 演示如何配合 `spring` 动画规格(`AnimationSpec`),创建具有物理回弹效果的动画。
+- `withDIYBezier`: 演示如何使用 `tween` 和自定义的 `CubicBezierEasing` (贝塞尔曲线),来创建非线性的、自定义缓动曲线的动画。
+- `withKeyframesSpline`: 演示如何使用 `keyframesWithSpline` 定义一个基于关键帧的复杂动画,允许在不同时间点达到不同的值,并使用缓动曲线平滑过渡。
+- `withInfinityRepeatable`: 演示如何结合 `infiniteRepeatable` 创建一个无限重复的复杂关键帧动画。
+- `withSnap`: 演示如何使用 `snap` 动画规格,使值瞬间变化,无任何过渡效果。
+
+#### 2. `infiniteTransition.kt`
+
+该文件通过一个名为 `infiniteTransition` 的单例对象,提供了两个 `@Composable` 函数,专门用于演示 `rememberInfiniteTransition` 的用法。此 API 用于创建在 Composable 进入组合时自动开始且永不停止的动画。
+
+**主要演示内容包括:**
+
+- `animateColor`: 演示如何使用 `infiniteTransition.animateColor` 创建一个在两种颜色(灰色和红色)之间无限循环、平滑过渡的颜色动画。
+- `animateFloat`: 演示如何使用 `infiniteTransition.animateFloat` 创建一个使组件尺寸(宽度)在两个浮点值(100f 和 300f)之间无限循环、平滑变化的动画。
+
+---
+
+### `singel` 文件夹分析
+
+`singel` 文件夹包含了更高阶、更易用的 Jetpack Compose 动画 API 示例。这些 API 通常用于实现单一值的状态驱动动画或特定的通用动画效果。
+
+#### 目录结构
+
+```
+singel/
+├── animateColorAsState.kt
+├── animateContentSize.kt
+├── animateContentVisibility.kt
+├── animateOpacityAsState.kt
+├── shareElement.kt
+├── shareTransition.kt
+└── updateTransition.kt
+```
+
+#### 文件内容总结
+
+- **`animateColorAsState.kt`**: 演示了 `animateColorAsState` 的用法。当目标颜色状态(`targetValue`)改变时,它会自动创建一个颜色过渡动画。这是实现平滑颜色变化最简单的方法。
+
+- **`animateOpacityAsState.kt`**: 演示了 `animateFloatAsState` 的一个具体应用场景——控制透明度。当目标透明度值(0f 或 1f)改变时,它会平滑地改变组件的 `alpha` 值。
+
+- **`animateContentSize.kt`**: 演示了 `Modifier.animateContentSize()`。将此修饰符应用于一个组件,当该组件的尺寸因其内容或状态改变而发生变化时,尺寸变化过程会自动产生动画效果。
+
+- **`animateContentVisibility.kt`**: 演示了 `AnimatedContent` 和 `animateEnterExit` 修饰符,用于在内容出现或消失时应用淡入(`fadeIn`)和淡出(`fadeOut`)动画。
+
+- **`updateTransition.kt`**: 演示了 `updateTransition` 的用法。当需要根据一个状态(如 `blueState`)同时更新多个动画值(如颜色和尺寸)时,`updateTransition` 是一个理想的选择。它能确保所有相关的动画同步进行。
+
+- **`shareTransition.kt` & `shareElement.kt`**: 这两个文件演示了实验性的 `SharedTransitionLayout` API,用于实现**共享元素转场动画**。
+ - `shareTransition.kt` 展示了如何使用 `sharedBounds` 和 `sharedElement` 在不同布局状态(`Row` 和 `Column`)之间共享组件的边界和内容,创建平滑的布局转换效果。
+ - `shareElement.kt` 专注于在不同状态下共享单个 `Image` 组件,并改变其尺寸和形状,实现类似 Activity 转场中的共享元素效果。
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/animatable.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/animatable.kt
new file mode 100644
index 0000000000..a92424b114
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/animatable.kt
@@ -0,0 +1,276 @@
+package com.baidaidai.animora.components.animation.demo
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.keyframes
+import androidx.compose.animation.core.keyframesWithSpline
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.delay
+
+object animatable {
+
+ /**
+ * 演示 [Animatable.animateTo] 的基本用法。
+ *
+ * 这个 Composable 会根据 [blueState] 的状态来驱动一个 [Box] 的透明度(alpha)动画。
+ * 当 `blueState` 为 true 时,它会将 alpha 动画到 0.5,等待一秒,然后再动画到 0。
+ * 当 `blueState` 为 false 时,它会将 alpha 动画回 1。
+ *
+ * @param blueState 一个布尔值状态,用于触发动画。
+ */
+ @Composable
+ fun AnimateTo(blueState: Boolean){
+ var alphaValue = remember { Animatable(1f) }
+ LaunchedEffect(blueState) {
+ if (blueState){
+ alphaValue.animateTo(0.5f)
+ delay(1000)
+ alphaValue.animateTo(0f)
+ }else{
+ alphaValue.animateTo(1f)
+ }
+ }
+ Column {
+ Box(
+ modifier = Modifier
+ .size(100.dp)
+ .alpha(alphaValue.value)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+ }
+
+ /**
+ * 演示 [Animatable] 如何与 [spring] 动画规格结合使用。
+ *
+ * 这个 Composable 会使用一个具有中等回弹效果([Spring.DampingRatioMediumBouncy])
+ * 和中等刚度([Spring.StiffnessMedium])的弹簧动画来驱动 [Box] 的宽度变化。
+ * 动画由 [blueState] 的变化触发。
+ *
+ * @param blueState 一个布尔值状态,用于触发动画。
+ */
+ @Composable
+ fun withMediumSpringSpec(blueState: Boolean){
+ var widthValue = remember { Animatable(100f) }
+ LaunchedEffect(blueState) {
+ if (blueState){
+ widthValue.animateTo(
+ targetValue = 50f,
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioMediumBouncy,
+ stiffness = Spring.StiffnessMedium
+ )
+ )
+ delay(1000)
+ widthValue.animateTo(
+ targetValue = 300f,
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioMediumBouncy,
+ stiffness = Spring.StiffnessMedium
+ )
+ )
+ }else{
+ widthValue.animateTo(
+ targetValue = 100f,
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioMediumBouncy,
+ stiffness = Spring.StiffnessMedium
+ )
+ )
+ }
+ }
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp,width = widthValue.value.dp)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+ }
+
+ /**
+ * 演示 [Animatable] 如何与自定义的 [CubicBezierEasing] 缓动曲线结合使用。
+ *
+ * 这个 Composable 使用一个 [tween] 动画规格,并结合一个自定义的贝塞尔曲线,
+ * 来创建一个独特的缓动效果,用于驱动 [Box] 的宽度变化。
+ *
+ * @param blueState 一个布尔值状态,用于触发动画。
+ */
+ @Composable
+ fun withDIYBezier(blueState: Boolean){
+ var widthValue = remember { Animatable(100f) }
+ LaunchedEffect(blueState) {
+ val animSpec = tween(
+ durationMillis = 1000,
+ easing = CubicBezierEasing(1.00f, 0.00f, 0.00f, 1.00f)
+ )
+ if (blueState){
+ widthValue.animateTo(
+ targetValue = 50f,
+ animationSpec = animSpec
+ )
+ delay(1000)
+ widthValue.animateTo(
+ targetValue = 300f,
+ animationSpec = animSpec
+ )
+ }else{
+ widthValue.animateTo(
+ targetValue = 100f,
+ animationSpec = animSpec
+ )
+ }
+ }
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp,width = widthValue.value.dp)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+ }
+
+ /**
+ * 演示 [Animatable] 如何与 [keyframesWithSpline] 动画结合使用。
+ *
+ * 这个 Composable 通过一系列关键帧来驱动 [Box] 的宽度动画。
+ * 动画会在给定的时间点之间,使用样条曲线平滑地插值宽度值,
+ * 从而对动画的进程提供精细的控制。
+ *
+ * @param blueState 一个布尔值状态,用于触发动画。
+ */
+ @Composable
+ fun withKeyframesSpline(blueState: Boolean) {
+ val widthValue = remember { Animatable(100f) }
+ LaunchedEffect(blueState) {
+ val animSpec = keyframesWithSpline {
+ durationMillis = 3000 // 总动画时长
+ 50f at 500 using CubicBezierEasing(1.00f, 0.00f, 0.00f, 1.00f)
+ 150f at 2000 using CubicBezierEasing(1.00f, 0.00f, 0.00f, 1.00f)
+ 300f at 3000 using CubicBezierEasing(1.00f, 0.00f, 0.00f, 1.00f)
+ }
+
+ if (blueState) {
+ widthValue.animateTo(
+ targetValue = 300f, // 最终目标值
+ animationSpec = animSpec
+ )
+ } else {
+ widthValue.animateTo(
+ targetValue = 100f,
+ animationSpec = snap()
+ )
+ }
+ }
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp, width = widthValue.value.dp)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+ }
+
+ /**
+ * 演示 [Animatable] 如何与 [infiniteRepeatable] 动画结合使用。
+ *
+ * 这个 Composable 使用一个无限重复的关键帧动画来驱动 [Box] 的宽度变化,
+ * 并在每个周期结束时反转方向。
+ * 注意:虽然 `Animatable` 可以启动一个无限动画,但这会阻塞协程。
+ * 对于“即发即忘”的无限动画,[rememberInfiniteTransition] 通常是更好的选择。
+ *
+ * @param blueState 一个布尔值状态,用于触发动画。
+ */
+ @Composable
+ fun withInfinityRepeatable(blueState: Boolean) {
+ val widthValue = remember { Animatable(100f) }
+
+ LaunchedEffect(blueState) {
+ if (blueState) {
+ widthValue.animateTo(
+ targetValue = 300f,
+ animationSpec = infiniteRepeatable(
+ animation = keyframes {
+ durationMillis = 3000
+ 50f at 500 using CubicBezierEasing(1.00f, 0.00f, 0.00f, 1.00f)
+ 150f at 2000 using CubicBezierEasing(1.00f, 0.00f, 0.00f, 1.00f)
+ 300f at 3000 using CubicBezierEasing(1.00f, 0.00f, 0.00f, 1.00f)
+ },
+ repeatMode = RepeatMode.Reverse
+ )
+ )
+ } else {
+ widthValue.animateTo(
+ targetValue = 100f,
+ animationSpec = snap()
+ )
+ }
+ }
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp, width = widthValue.value.dp)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+ }
+
+ /**
+ * 演示 [Animatable] 如何与 [snap] 动画规格结合使用。
+ *
+ * 这个 Composable 会立即改变 [Box] 的宽度,而没有任何动画过渡。
+ * `snap` 规格对于那些不需要过渡的即时状态变更非常有用。
+ *
+ * @param blueState 一个布尔值状态,用于触发动画。
+ */
+ @Composable
+ fun withSnap(blueState: Boolean) {
+ val widthValue = remember { Animatable(100f) }
+
+ LaunchedEffect(blueState) {
+ if (blueState) {
+ widthValue.animateTo(
+ targetValue = 300f,
+ animationSpec = snap()
+ )
+ } else {
+ widthValue.animateTo(
+ targetValue = 100f,
+ animationSpec = snap()
+ )
+ }
+ }
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp, width = widthValue.value.dp)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+ }
+}
+
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/infiniteTransition.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/infiniteTransition.kt
new file mode 100644
index 0000000000..be9394c2c2
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/infiniteTransition.kt
@@ -0,0 +1,87 @@
+package com.baidaidai.animora.components.animation.demo
+
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.EaseInOut
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+object infiniteTransition {
+
+ /**
+ * 演示如何使用 [rememberInfiniteTransition] 创建一个无限循环的颜色动画。
+ *
+ * 这个 Composable 使用 `infiniteTransition.animateColor` 来驱动一个 [Box] 的背景颜色,
+ * 使其在灰色和红色之间平滑地、无限地来回过渡。
+ * 动画规格使用了 `tween` 和 `RepeatMode.Reverse`。
+ */
+ @Composable
+ fun animateColor(){
+ val infiniteTransition = rememberInfiniteTransition(label = "infiniteTransition Demo")
+ val bgColor:Color by infiniteTransition.animateColor(
+ initialValue = Color.Gray,
+ targetValue = Color.Red,
+ animationSpec = infiniteRepeatable(
+ animation = tween(
+ durationMillis = 500,
+ easing = EaseInOut
+ ),
+ repeatMode = RepeatMode.Reverse
+ )
+ )
+ Column {
+ Box(
+ modifier = Modifier
+ .size(100.dp)
+ .background(color = bgColor),
+ content = {}
+ )
+ }
+ }
+
+ /**
+ * 演示如何使用 [rememberInfiniteTransition] 创建一个无限循环的浮点值动画。
+ *
+ * 这个 Composable 使用 `infiniteTransition.animateFloat` 来驱动一个 [Box] 的宽度,
+ * 使其在 100.dp 和 300.dp 之间平滑地、无限地来回变化。
+ * 动画规格使用了 `tween` 和 `RepeatMode.Reverse`。
+ */
+ @Composable
+ fun animateFloat(){
+ val infiniteTransition = rememberInfiniteTransition(label = "infiniteTransition Demo")
+ val size by infiniteTransition.animateFloat(
+ initialValue = 100f,
+ targetValue = 300f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(
+ durationMillis = 2000,
+ easing = EaseInOut
+ ),
+ repeatMode = RepeatMode.Reverse
+ )
+
+ )
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp, width = size.dp)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+
+ }
+
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateColorAsState.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateColorAsState.kt
new file mode 100644
index 0000000000..c015b96fe9
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateColorAsState.kt
@@ -0,0 +1,41 @@
+package com.baidaidai.animora.components.animation.demo.singel
+
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+/**
+ * 演示 [animateColorAsState] 的用法。
+ *
+ * 当 [blueState] 改变时,这个 Composable 会自动地、平滑地
+ * 在灰色和红色之间创建颜色过渡动画。
+ *
+ * @param blueState 一个布尔值状态,用于在两种颜色之间切换。
+ */
+@Composable
+fun animateColorAsState(
+ blueState: Boolean
+){
+ val color by animateColorAsState(
+ targetValue = if (blueState) Color.Red else Color.Gray
+ )
+ Column {
+ Box(
+ modifier = Modifier
+ .size(100.dp)
+ .clip(MaterialTheme.shapes.small)
+ .background(color = color)
+ ,
+ content = {}
+ )
+ }
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateContentSize.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateContentSize.kt
new file mode 100644
index 0000000000..f822c13c6b
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateContentSize.kt
@@ -0,0 +1,35 @@
+package com.baidaidai.animora.components.animation.demo.singel
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+/**
+ * 演示 [Modifier.animateContentSize] 的用法。
+ *
+ * 当 [Box] 的尺寸根据 [blueState] 改变时,
+ * `animateContentSize` 修饰符会自动为尺寸变化创建动画效果。
+ *
+ * @param blueState 一个布尔值状态,用于改变 [Box] 的宽度。
+ */
+@Composable
+fun animateContentSize(blueState: Boolean){
+ AnimatedContent(blueState) { blueState ->
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp, width = if(blueState) 300.dp else 100.dp)
+ .background(color = Color.Gray)
+ .animateContentSize(),
+ content = {}
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateContentVisibility.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateContentVisibility.kt
new file mode 100644
index 0000000000..45da35908f
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateContentVisibility.kt
@@ -0,0 +1,41 @@
+package com.baidaidai.animora.components.animation.demo.singel
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+/**
+ * 演示如何使用 [AnimatedContent] 和 `animateEnterExit` 来创建内容的出现和消失动画。
+ *
+ * 当 [blueState] 为 false 时,[Box] 会带着淡入([fadeIn])效果出现。
+ * 当 [blueState] 变为 true 时,[Box] 会带着淡出([fadeOut])效果消失。
+ *
+ * @param blueState 一个布尔值状态,用于控制 [Box] 的可见性。
+ */
+@Composable
+fun animateContentVisibility(blueState: Boolean) {
+ AnimatedContent(blueState) { blueState ->
+ if (!blueState){
+ Column {
+ Box(
+ modifier = Modifier
+ .size(100.dp)
+ .background(color = Color.Gray)
+ .animateEnterExit(
+ enter = fadeIn(),
+ exit = fadeOut()
+ ),
+ content = {}
+ )
+ }
+ }
+ }
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateOpacityAsState.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateOpacityAsState.kt
new file mode 100644
index 0000000000..23beb41dc9
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/animateOpacityAsState.kt
@@ -0,0 +1,39 @@
+package com.baidaidai.animora.components.animation.demo.singel
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+/**
+ * 演示 [animateFloatAsState] 在控制透明度(alpha)时的用法。
+ *
+ * 当 [blueState] 改变时,这个 Composable 会自动地、平滑地
+ * 在完全不透明(1f)和完全透明(0f)之间创建过渡动画。
+ *
+ * @param blueState 一个布尔值状态,用于在两种透明度之间切换。
+ */
+@Composable
+fun animateOpacityAsState(
+ blueState: Boolean
+){
+ val alpha by animateFloatAsState(
+ targetValue = if (blueState) 0f else 1f
+ )
+ Column {
+ Box(
+ modifier = Modifier
+ .size(100.dp)
+ .alpha(alpha)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/shareElement.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/shareElement.kt
new file mode 100644
index 0000000000..bbd7069be0
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/shareElement.kt
@@ -0,0 +1,75 @@
+package com.baidaidai.animora.components.animation.demo.singel
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionLayout
+import androidx.compose.animation.core.EaseInOut
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.R
+
+/**
+ * 演示实验性的 [SharedTransitionLayout] 和 [Modifier.sharedElement] API。
+ *
+ * 这个 Composable 展示了如何在两种不同状态之间共享一个 [Image] 组件。
+ * 当 [blueState] 改变时,图片会在两种不同尺寸和裁剪形状之间平滑地过渡,
+ * 创造出共享元素转场的效果。
+ *
+ * @param blueState 一个布尔值状态,用于在两种布局状态之间切换。
+ */
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+fun sharedElement(blueState: Boolean){
+ SharedTransitionLayout {
+ AnimatedContent(
+ targetState = blueState,
+ transitionSpec = { fadeIn() togetherWith fadeOut() }
+ ){ blueState ->
+ if (blueState){
+ Image(
+ painter = painterResource(R.drawable.demo_tree),
+ contentDescription = "A image",
+ modifier = Modifier
+ .sharedElement(
+ sharedContentState = rememberSharedContentState("image"),
+ animatedVisibilityScope = this@AnimatedContent
+ )
+ .size(200.dp)
+ .clip(shape = CircleShape)
+ )
+ }else{
+ Image(
+ painter = painterResource(R.drawable.demo_tree),
+ contentDescription = "A image",
+ modifier = Modifier
+ .sharedElement(
+ sharedContentState = rememberSharedContentState("image"),
+ animatedVisibilityScope = this@AnimatedContent
+ )
+ .size(100.dp)
+ .clip(shape = CircleShape)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/shareTransition.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/shareTransition.kt
new file mode 100644
index 0000000000..eae80b74a8
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/shareTransition.kt
@@ -0,0 +1,94 @@
+package com.baidaidai.animora.components.animation.demo.singel
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionLayout
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+
+/**
+ * 演示实验性的 [SharedTransitionLayout] 和 [Modifier.sharedBounds] API。
+ *
+ * 这个 Composable 展示了如何在不同布局([Row] 和 [Column])之间
+ * 共享一组 UI 元素的边界和内容。当 [blueState] 改变时,
+ * 容器和内部的 [Text] 元素会平滑地过渡到新的位置和布局。
+ *
+ * @param blueState 一个布尔值状态,用于在两种布局状态之间切换。
+ */
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+fun shareTransition(blueState: Boolean){
+ SharedTransitionLayout {
+ AnimatedContent(blueState){ blueState ->
+ if (blueState){
+ Row(
+ modifier = Modifier
+ .border(1.dp, color = Color.Black)
+ .sharedBounds(
+ sharedContentState = rememberSharedContentState("Border"),
+ animatedVisibilityScope = this@AnimatedContent
+ )
+ .fillMaxWidth()
+ ,
+ ){
+ Text(
+ text = "Title",
+ modifier = Modifier
+ .sharedElement(
+ sharedContentState = rememberSharedContentState("Title"),
+ animatedVisibilityScope = this@AnimatedContent
+ )
+ )
+ Text(
+ text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l",
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier
+ .sharedElement(
+ sharedContentState = rememberSharedContentState("Content"),
+ animatedVisibilityScope = this@AnimatedContent
+ )
+ )
+ }
+ }else{
+ Column(
+ modifier = Modifier
+ .border(1.dp, color = Color.Black)
+ .sharedBounds(
+ sharedContentState = rememberSharedContentState("Border"),
+ animatedVisibilityScope = this@AnimatedContent
+ )
+ .size(100.dp)
+ ,
+ ){
+ Text(
+ text = "Title",
+ modifier = Modifier
+ .sharedElement(
+ sharedContentState = rememberSharedContentState("Title"),
+ animatedVisibilityScope = this@AnimatedContent
+ )
+ )
+ Text(
+ text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l",
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier
+ .sharedElement(
+ sharedContentState = rememberSharedContentState("Content"),
+ animatedVisibilityScope = this@AnimatedContent
+ )
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/updateTransition.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/updateTransition.kt
new file mode 100644
index 0000000000..332a5c792f
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/demo/singel/updateTransition.kt
@@ -0,0 +1,50 @@
+package com.baidaidai.animora.components.animation.demo.singel
+
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+/**
+ * 演示 [updateTransition] 的用法,用于同步多个值的动画。
+ *
+ * 当 [blueState] 改变时,[updateTransition] 会创建一个“过渡”(transition),
+ * 然后使用 `animateColor` 和 `animateDp` 来同时驱动 [Box] 的背景颜色和宽度的动画。
+ * 这确保了所有相关的动画都是同步进行的。
+ *
+ * @param blueState 一个布尔值状态,作为驱动所有动画的目标状态。
+ */
+@Composable
+fun _updateTransition(blueState: Boolean){
+ val genLock = updateTransition(blueState)
+ val contentColor by genLock.animateColor { blueState->
+ if (blueState){
+ Color.Red
+ }else{
+ Color.Gray
+ }
+ }
+ val contentSize by genLock.animateDp { blueState ->
+ if (blueState){
+ 300.dp
+ }else{
+ 100.dp
+ }
+ }
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp, width = contentSize)
+ .background(color = contentColor),
+ content = {}
+ )
+ }
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/detail/NecessaryComponents.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/detail/NecessaryComponents.kt
new file mode 100644
index 0000000000..0e01993f39
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/detail/NecessaryComponents.kt
@@ -0,0 +1,95 @@
+package com.baidaidai.animora.components.animation.detail
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Animation
+import androidx.compose.material.icons.outlined.ArrowBackIosNew
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExtendedFloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.LocalAnimatedContentScope
+import com.baidaidai.animora.LocalSharedTransitionScope
+
+final object NecessaryComponents {
+ /**
+ * 动画详情页面的顶部应用栏(TopAppBar)。
+ *
+ * 这个 Composable 包含了返回按钮和标题。标题部分利用了 `sharedBounds`
+ * 来支持共享元素转场动画,使其能在不同屏幕间平滑过渡。
+ *
+ * @param content 顶部应用栏要显示的标题文本。
+ * @param onClick 返回按钮的点击事件回调。
+ */
+ @OptIn(
+ ExperimentalMaterial3Api::class,
+ ExperimentalSharedTransitionApi::class
+ )
+ @Composable
+ fun animationDetailsTopAppBar(
+ content: String,
+ onClick: () -> Unit,
+ ){
+ val sharedTransitionScope = LocalSharedTransitionScope.current
+ val animatedContentScope = LocalAnimatedContentScope.current
+ with(sharedTransitionScope){
+ TopAppBar(
+ title = {
+ Text(
+ text = content,
+ modifier = Modifier
+ .sharedBounds(
+ sharedContentState = rememberSharedContentState("AnimationTitle-${content}"),
+ animatedVisibilityScope = animatedContentScope
+ )
+ )
+ },
+ navigationIcon = {
+ IconButton(
+ onClick = onClick
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.ArrowBackIosNew,
+ contentDescription = "Back"
+ )
+ }
+ }
+ )
+ }
+ }
+
+ /**
+ * 动画详情页面的悬浮操作按钮(FloatingActionButton)。
+ *
+ * 这个 Composable 用于触发示例动画的开始。
+ *
+ * @param onClick 按钮的点击事件回调,通常用于启动或重置动画。
+ */
+ @Composable
+ fun animationDetailsFloatActionButton(
+ onClick:()-> Unit
+ ){
+ ExtendedFloatingActionButton(
+ onClick = onClick,
+ icon = { Icon(Icons.Outlined.Animation, "Click To Start Animation") },
+ text = { Text(text = "Start Animation") },
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = Modifier
+ .offset(
+ y = (-10).dp,
+ x = (-15).dp
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/animation/detail/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/detail/index.kt
new file mode 100644
index 0000000000..b60c42abeb
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/animation/detail/index.kt
@@ -0,0 +1,140 @@
+package com.baidaidai.animora.components.animation.detail
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardColors
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.runtime.*
+import androidx.compose.ui.platform.LocalContext
+import androidx.navigation.NavController
+import com.baidaidai.animora.LocalAnimationViewModel
+import com.baidaidai.animora.shared.viewModel.blueStateViewModel
+import dev.jeziellago.compose.markdowntext.MarkdownText
+
+/**
+ * 作为 AnimationDetails 页面入口的容器型 Composable。
+ *
+ * 该函数负责组合动画展示与说明内容,
+ * 并协调动画触发状态与页面导航行为。
+ *
+ * ## 页面结构
+ * - 使用独立的 [Scaffold] 作为页面基底
+ * - 顶部栏与悬浮按钮用于控制页面导航与动画状态
+ * - 页面主体由动画展示区域与说明文档组成
+ *
+ * ## 状态与数据来源
+ * - 动画触发状态由 [blueStateViewModel] 提供并控制
+ * - 当前展示的动画与其说明内容来自 [LocalAnimationViewModel]
+ * - 动画说明文档以 Markdown 形式从 assets 中加载并渲染
+ *
+ * 该 Composable 不负责创建或管理任何 ViewModel,
+ * 仅消费上层已提供的状态与数据。
+ *
+ * @param blueStateViewModel 控制动画触发状态的 ViewModel
+ * @param navController 用于处理页面返回行为的 [NavController]
+ *
+ * @see blueStateViewModel
+ * @see LocalAnimationViewModel
+ * @see NavController
+ */
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+fun animationDetailContainer(
+ blueStateViewModel: blueStateViewModel,
+ navController: NavController,
+){
+ val animationDatasViewModel = LocalAnimationViewModel.current
+ val blueStateViewModel = blueStateViewModel
+
+ val blueState by blueStateViewModel.blueState.collectAsState()
+ val animationDatas by animationDatasViewModel.selectedAnimation.collectAsState()
+
+ val assetsManager = LocalContext.current.assets
+ var inputStream = assetsManager.open(animationDatas.details)
+ var markdownContent = inputStream.bufferedReader().use { it.readText() }
+
+ val scrollableState = rememberScrollableState { it }
+
+ Scaffold(
+ topBar = {
+ NecessaryComponents.animationDetailsTopAppBar(
+ content = animationDatas.name,
+ ) {
+ navController.popBackStack()
+ blueStateViewModel.changeBlueState(false)
+ }
+ },
+ floatingActionButton = {
+ NecessaryComponents.animationDetailsFloatActionButton {
+ blueStateViewModel.changeBlueState(!blueState)
+ }
+ }
+ ){ contentPaddingValues->
+ Column(
+ modifier = Modifier
+ .padding(contentPaddingValues)
+ .scrollable(
+ state = rememberScrollableState { 1f },
+ orientation = Orientation.Vertical
+ )
+ .padding(horizontal = 30.dp)
+ ){
+ Card(
+ colors = CardColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ disabledContainerColor = MaterialTheme.colorScheme.error,
+ disabledContentColor = MaterialTheme.colorScheme.onError
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Box(
+ modifier = Modifier
+ .padding(30.dp)
+ ) {
+ animationDatas.animationFiles(blueStateViewModel.blueState.collectAsState().value)
+ }
+ }
+ HorizontalDivider(
+ thickness = 3.dp,
+ modifier = Modifier
+ .padding(
+ top = 10.dp,
+ bottom = 10.dp,
+ start = 5.dp,
+ end = 5.dp
+ )
+ )
+ MarkdownText(
+ markdown = markdownContent,
+ modifier = Modifier
+ .verticalScroll(
+ state = rememberScrollState()
+ )
+ .padding(
+ bottom = 100.dp
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/info/authorArea.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/info/authorArea.kt
new file mode 100644
index 0000000000..faa3bc0851
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/info/authorArea.kt
@@ -0,0 +1,90 @@
+package com.baidaidai.animora.components.info
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Person
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedCard
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.R
+
+/**
+ * “关于”页面中的作者信息区域容器。
+ *
+ * 这个 Composable 函数以卡片的形式展示了作者信息,
+ * 包括作者名、Twitter 链接和 Github 链接。
+ *
+ * @param onClickGithub 当点击 "Follow my Github" 时触发的回调。
+ * @param onClickTwitter 当点击 "Follow my Twitter" 时触发的回调。
+ */
+@Composable
+fun authorAreaContainer(
+ onClickGithub:()-> Unit,
+ onClickTwitter: () -> Unit
+){
+ OutlinedCard {
+ Column {
+ Text(
+ text = "Author Area",
+ modifier = Modifier
+ .padding(start = 15.dp, top = 10.dp),
+ style = MaterialTheme.typography.titleSmall,
+ )
+ ListItem(
+ headlineContent = {
+ Text("Author")
+ },
+ supportingContent = {
+ Text("Creater. Bai")
+ },
+ leadingContent = {
+ Icon(
+ Icons.Outlined.Person,
+ contentDescription = "Application Version Icons"
+ )
+ }
+ )
+ ListItem(
+ headlineContent = {
+ Text("Follow my Twitter")
+ },
+ leadingContent = {
+ Icon(
+ painter = painterResource(R.drawable.twitter),
+ contentDescription = "Application Version Icons",
+ modifier = Modifier
+ .size(24.dp)
+ .padding(2.dp)
+ )
+ },
+ modifier = Modifier
+ .clickable(onClick = onClickTwitter)
+ )
+ ListItem(
+ headlineContent = {
+ Text("Follow my Github")
+ },
+ leadingContent = {
+ Icon(
+ painter = painterResource(R.drawable.github),
+ contentDescription = "Application Version Icons",
+ modifier = Modifier
+ .size(24.dp)
+ .padding(2.dp)
+ )
+ },
+ modifier = Modifier
+ .clickable(onClick = onClickGithub)
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/info/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/info/index.kt
new file mode 100644
index 0000000000..f5c0520fe4
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/info/index.kt
@@ -0,0 +1,73 @@
+package com.baidaidai.animora.components.info
+
+import android.app.Activity
+import android.net.Uri
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.platform.UriHandler
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.R
+import com.baidaidai.animora.components.info.infoScreen.NecessaryComponents
+
+/**
+ * Animora 应用的 Info 页面容器型 Composable。
+ *
+ * 该函数负责展示应用信息,包括:
+ * - 应用版本号和项目地址
+ * - 作者介绍及社交链接(GitHub、Twitter)
+ *
+ * @see Scaffold
+ * @see LocalUriHandler
+ */
+@Composable
+fun infoScreen(){
+ val activity = LocalContext.current as Activity
+ val uriHandler = LocalUriHandler.current as UriHandler
+ Scaffold(
+ topBar = {
+ NecessaryComponents.infoScreenTopAppBar {
+ activity.finish()
+ }
+ }
+ ){ contentPadding ->
+ Column(
+ modifier = Modifier
+ .padding(contentPadding)
+ .padding(horizontal = 20.dp)
+ ){
+ infoAreaContainer {
+ uriHandler.openUri(
+ uri = "https://github.com/Baidaidai-GFWD-origin/Animora"
+ )
+ }
+ Spacer(modifier = Modifier.size(height = 10.dp, width = 1.dp))
+ authorAreaContainer(
+ onClickGithub = {
+ uriHandler.openUri(
+ uri = "https://github.com/Baidaidai-GFWD-origin"
+ )
+ },
+ onClickTwitter = {
+ uriHandler.openUri(
+ uri = "https://x.com/creater_bai"
+ )
+ }
+ )
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun infoScreenPreview(){
+ infoScreen()
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/info/infoArea.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/info/infoArea.kt
new file mode 100644
index 0000000000..4406fa5c73
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/info/infoArea.kt
@@ -0,0 +1,111 @@
+package com.baidaidai.animora.components.info
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedCard
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.R
+import kotlinx.coroutines.launch
+
+/**
+ * “关于”页面中的应用信息区域容器。
+ *
+ * 这个 Composable 函数以卡片的形式展示了应用信息,
+ * 包括应用 Logo、名称、版本号以及一个指向 Github 的链接。
+ *
+ * @param onClick 当点击 "View in Github" 时触发的回调。
+ */
+@Composable
+fun infoAreaContainer(
+ onClick: ()->Unit
+){
+ val coroutineScope = rememberCoroutineScope()
+ OutlinedCard {
+ Column {
+ Row(
+ modifier = Modifier
+ .padding(start = 20.dp, top = 20.dp),
+ ){
+ Image(
+ painter = painterResource(R.drawable.animation_icon),
+ contentDescription = "Logo",
+ modifier = Modifier
+ .size(50.dp)
+ .clip(CircleShape)
+ .background(
+ color = Color(0xD1,0xB6,0xE4,0xFF)
+ )
+ .padding(10.dp)
+ )
+ Text(
+ text = stringResource(R.string.app_name),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier
+ .height(50.dp)
+ .padding(start = 15.dp)
+ .wrapContentSize(),
+ )
+ }
+ HorizontalDivider(
+ thickness = 1.dp,
+ modifier = Modifier
+ .padding(horizontal = 20.dp)
+ .padding(top = 20.dp)
+ )
+ ListItem(
+ headlineContent = {
+ Text("Version")
+ },
+ supportingContent = {
+ Text(
+ text = stringResource(R.string.app_version_local)
+ )
+ },
+ leadingContent = {
+ Icon(
+ painter = painterResource(R.drawable.outline_apk_document_24),
+ contentDescription = "Application Version Icons"
+ )
+ }
+ )
+ ListItem(
+ headlineContent = {
+ Text("View in Github")
+ },
+ leadingContent = {
+ Icon(
+ painter = painterResource(R.drawable.github),
+ contentDescription = "View in Github",
+ modifier = Modifier
+ .size(24.dp)
+ .padding(2.dp)
+ )
+ },
+ modifier = Modifier
+ .clickable(
+ onClick = onClick
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/info/infoScreen/NecessaryComponents.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/info/infoScreen/NecessaryComponents.kt
new file mode 100644
index 0000000000..b87c93c520
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/info/infoScreen/NecessaryComponents.kt
@@ -0,0 +1,45 @@
+package com.baidaidai.animora.components.info.infoScreen
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ArrowBackIosNew
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+
+object NecessaryComponents {
+ /**
+ * “关于”页面的顶部应用栏(TopAppBar)。
+ *
+ * 这个 Composable 包含一个返回按钮和一个固定的标题 "About"。
+ *
+ * @param onClick 返回按钮的点击事件回调。
+ */
+ @OptIn(
+ ExperimentalMaterial3Api::class,
+ ExperimentalMaterial3ExpressiveApi::class
+ )
+ @Composable
+ fun infoScreenTopAppBar(
+ onClick: ()-> Unit
+ ){
+ TopAppBar(
+ navigationIcon = {
+ IconButton(
+ onClick = onClick
+ ) {
+ Icon(
+ Icons.Outlined.ArrowBackIosNew,
+ contentDescription = "Back"
+ )
+ }
+ },
+ title = {
+ Text("About")
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/spring/animationStudio/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/animationStudio/index.kt
new file mode 100644
index 0000000000..cc9ee40dae
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/animationStudio/index.kt
@@ -0,0 +1,96 @@
+package com.baidaidai.animora.components.spring.animationStudio
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.components.spring.model.springSpecParameterData
+import com.baidaidai.animora.components.spring.model.springSpecStudioViewModel
+
+/**
+ * 根据输入参数提供一个动画规格([AnimationSpec])。
+ *
+ * 如果启用了 Spring 动画,它会返回一个根据 [springSpecValue] 配置的 [spring] 规格。
+ * 否则,它返回一个默认的 [tween] 规格。
+ *
+ * @param enableSpring 一个布尔值,决定是否使用 [spring] 动画。
+ * @param springSpecValue 包含 `dampingRatio` 和 `stiffness` 的数据类。
+ * @return 返回一个 [AnimationSpec]。
+ */
+fun animationProvider(
+ enableSpring: Boolean,
+ springSpecValue: springSpecParameterData
+):AnimationSpec {
+ when(enableSpring){
+ true -> {
+ return spring(
+ dampingRatio = springSpecValue.dampingRatio,
+ stiffness = springSpecValue.stiffness,
+ visibilityThreshold = null
+ )
+ }
+ else -> {
+ return tween()
+ }
+ }
+}
+
+/**
+ * Spring 动画工作室的动画预览区域。
+ *
+ * 这个 Composable 函数根据 [springSpecStudioViewModel] 中提供的参数,
+ * 实时展示一个 [Box] 的宽度动画。动画的触发由 [blueState] 控制。
+ * 动画规格(spring 或 tween)由 `animationProvider` 函数提供。
+ *
+ * @param blueState 一个布尔值状态,用于触发动画的开始和结束。
+ * @param springSpecStudioViewModel 包含动画规格参数(如阻尼比、刚度)和是否启用 Spring 动画的状态的 ViewModel。
+ */
+@Composable
+fun animationStudio(
+ blueState: Boolean,
+ springSpecStudioViewModel: springSpecStudioViewModel
+){
+ var widthValue = remember { Animatable(100f) }
+ val springSpecValue by springSpecStudioViewModel.springSpecValue.collectAsState()
+ val enableSpring by springSpecStudioViewModel.enableSpring.collectAsState()
+
+ LaunchedEffect(blueState) {
+ if (blueState){
+ widthValue.animateTo(
+ targetValue = 300f,
+ animationSpec = animationProvider(
+ enableSpring = enableSpring,
+ springSpecValue = springSpecValue
+ )
+ )
+ }else{
+ widthValue.animateTo(
+ targetValue = 100f,
+ animationSpec = animationProvider(
+ enableSpring = enableSpring,
+ springSpecValue = springSpecValue
+ )
+ )
+ }
+ }
+ Column {
+ Box(
+ modifier = Modifier
+ .size(height = 100.dp,width = widthValue.value.dp)
+ .background(color = Color.Gray),
+ content = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/NecessaryComponents.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/NecessaryComponents.kt
new file mode 100644
index 0000000000..6ce4caff16
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/NecessaryComponents.kt
@@ -0,0 +1,77 @@
+package com.baidaidai.animora.components.spring.components
+
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.foundation.layout.offset
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Animation
+import androidx.compose.material.icons.outlined.ArrowBackIosNew
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExtendedFloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+object NecessaryComponents {
+ /**
+ * Spring 动画工作室页面的顶部应用栏(TopAppBar)。
+ *
+ * @param content 要在标题中显示的文本。
+ * @param onClick 返回按钮的点击事件回调。
+ */
+ @OptIn(
+ ExperimentalMaterial3Api::class,
+ ExperimentalSharedTransitionApi::class
+ )
+ @Composable
+ fun springSpecTopAppBar(
+ content: String,
+ onClick: () -> Unit
+
+ ){
+ TopAppBar(
+ title = {
+ Text(text = content)
+ },
+ navigationIcon = {
+ IconButton(
+ onClick = onClick
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.ArrowBackIosNew,
+ contentDescription = "Back"
+ )
+ }
+ }
+ )
+ }
+
+ /**
+ * Spring 动画工作室页面的悬浮操作按钮(FloatingActionButton)。
+ *
+ * 这个按钮用于触发预览动画的开始。
+ *
+ * @param onClick 按钮的点击事件回调。
+ */
+ @Composable
+ fun springSpecFloatActionButton(
+ onClick:()-> Unit
+ ){
+ ExtendedFloatingActionButton(
+ onClick = onClick,
+ icon = { Icon(Icons.Outlined.Animation, "Click To Start Animation") },
+ text = { Text(text = "Start Animation") },
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = Modifier.Companion
+ .offset(
+ y = (-10).dp,
+ x = (-15).dp
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/buttonGroups.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/buttonGroups.kt
new file mode 100644
index 0000000000..210fa8e420
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/buttonGroups.kt
@@ -0,0 +1,77 @@
+package com.baidaidai.animora.components.spring.components
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.ButtonGroup
+import androidx.compose.material3.ButtonGroupDefaults
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.ToggleButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * 一个在 [ButtonGroup] 内部使用的、可切换的按钮。 │
+ * │
+ * 这个 Composable 函数根据其在按钮组中的位置(第一个、中间或最后一个) │
+ * 自动应用不同的形状,以创建连接在一起的视觉效果。 │
+ * │
+ * @param list 包含所有按钮的列表,用于判断当前按钮的位置。 │
+ * @param index 当前按钮在 [list] 中的索引。 │
+ * @param content 按钮上显示的文本。 │
+ * @param checked 按钮当前是否处于选中状态。 │
+ * @param rowScope [RowScope] 的实例,用于在行布局中正确放置按钮。 │
+ * @param onCheckedChange 当按钮的选中状态改变时触发的回调。
+ */
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun toggleButton(
+ list:List<*>,
+ index: Int,
+ content: String,
+ checked: Boolean,
+ rowScope: RowScope,
+ onCheckedChange:()-> Unit
+){
+ with(rowScope){
+ ToggleButton(
+ checked = checked,
+ onCheckedChange = {
+ onCheckedChange()
+ },
+ shapes = when(index){
+ 0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
+ list.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
+ else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
+ },
+ modifier = Modifier
+ .height(32.dp)
+ .weight(
+ weight = if (checked) 1.5f else 1.2f
+ )
+ ) {
+ Text(
+ text = content,
+ style = MaterialTheme.typography.labelSmall
+ )
+ }
+ Spacer(
+ modifier = Modifier
+ .width(2.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/springSpecController.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/springSpecController.kt
new file mode 100644
index 0000000000..56217ea379
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/springSpecController.kt
@@ -0,0 +1,93 @@
+package com.baidaidai.animora.components.spring.components
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import com.baidaidai.animora.components.spring.model.list.dampingRatioTabRenderingList
+import com.baidaidai.animora.components.spring.model.list.stiffnessTabRenderingList
+import com.baidaidai.animora.components.spring.model.springSpecStudioViewModel
+import androidx.compose.runtime.mutableFloatStateOf
+import com.baidaidai.animora.components.spring.LocalSpringSpecStudioViewModel
+
+/**
+ * Spring 动画工作室的参数控制器。
+ *
+ * 这个 Composable 包含两组按钮组([toggleButton]),分别用于选择预设的
+ * `DampingRatio`(阻尼比)和 `Stiffness`(刚度)值。
+ * 当用户选择不同的预设值时,它会通过 [LocalSpringSpecStudioViewModel]
+ * 更新全局的 Spring 动画参数。
+ */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun springSpecStudioController(){
+
+ var dampingRatioTabSelected by rememberSaveable { mutableStateOf(0) }
+ var stiffNessTabSelected by rememberSaveable { mutableStateOf(0) }
+
+ var dampingRatioValue by rememberSaveable { mutableFloatStateOf(1f) }
+ var stiffNessValue by rememberSaveable { mutableFloatStateOf(50f) }
+
+ val springSpecStudioViewModel = LocalSpringSpecStudioViewModel.current
+ Column() {
+ Text(
+ text = "DampingRatio",
+ style = MaterialTheme.typography.titleSmall
+ )
+ Row(
+ modifier = Modifier
+ .padding(vertical = 10.dp)
+ ){
+ dampingRatioTabRenderingList.forEachIndexed { index, content ->
+ toggleButton(
+ list = dampingRatioTabRenderingList,
+ index = index,
+ content = content.featureName,
+ checked = dampingRatioTabSelected == index,
+ rowScope = this@Row
+ ){
+ dampingRatioTabSelected = index
+ dampingRatioValue = content.dampingRatioValue
+ springSpecStudioViewModel.changeSpringSpecValue(
+ dampingRatio = dampingRatioValue,
+ stiffness = stiffNessValue
+ )
+ }
+ }
+ }
+ Text(
+ text = "Stiffness",
+ style = MaterialTheme.typography.titleSmall
+ )
+ Row(
+ modifier = Modifier
+ .padding(vertical = 10.dp)
+ ){
+ stiffnessTabRenderingList.forEachIndexed { index, content ->
+ toggleButton(
+ list = stiffnessTabRenderingList,
+ index = index,
+ content = content.featureName,
+ checked = stiffNessTabSelected == index,
+ rowScope = this@Row
+ ){
+ stiffNessTabSelected = index
+ stiffNessValue = content.stiffnessValue
+ springSpecStudioViewModel.changeSpringSpecValue(
+ dampingRatio = dampingRatioValue,
+ stiffness = stiffNessValue
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/springSpecControllerHost.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/springSpecControllerHost.kt
new file mode 100644
index 0000000000..8b131eaac5
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/components/springSpecControllerHost.kt
@@ -0,0 +1,141 @@
+package com.baidaidai.animora.components.spring.components
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.core.EaseInOut
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ExpandLess
+import androidx.compose.material.icons.outlined.ExpandMore
+import androidx.compose.material3.Badge
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardColors
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.draw.rotate
+
+private val springSpecStudioControllerEnterTransition = slideInVertically(
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioLowBouncy,
+ stiffness = Spring.StiffnessHigh,
+ visibilityThreshold = null
+ ),
+ initialOffsetY = { 0 }
+)
+private val springSpecStudioControllerExitTransition = slideOutVertically(
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioLowBouncy,
+ stiffness = Spring.StiffnessHigh,
+ visibilityThreshold = null
+ ),
+ targetOffsetY = { 0 }
+)
+
+private val springSpecStudioControllerTransitionSpec = springSpecStudioControllerEnterTransition togetherWith springSpecStudioControllerExitTransition
+
+/**
+ * Spring 动画工作室的控制器宿主(Host)面板。
+ *
+ * 这个 Composable 函数以一个可展开/折叠的卡片形式,容纳了 [springSpecStudioController]。
+ * 它包含一个标题行和一个可点击的图标,用于控制内容的可见性。
+ * 内容的展开和折叠使用了自定义的 `slideInVertically` 和 `slideOutVertically` 动画。
+ */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun springSpecControllerHost(){
+ var springSpecControllerExpandedStatus by rememberSaveable { mutableStateOf(false) }
+ val expandedRotation by animateFloatAsState(
+ targetValue = if (springSpecControllerExpandedStatus) -180f else 0f,
+ animationSpec = tween(
+ durationMillis = 200,
+ easing = EaseInOut
+ ),
+ )
+
+ Card(
+ colors = CardColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ disabledContainerColor = MaterialTheme.colorScheme.error,
+ disabledContentColor = MaterialTheme.colorScheme.onError
+ )
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 15.dp, end = 15.dp)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Row {
+ Text(
+ text = "SpringSpec Controller",
+ style = MaterialTheme.typography.titleMedium,
+ )
+ Spacer(modifier = Modifier.padding(start = 3.dp))
+ Badge(){
+ Text("new")
+ }
+ }
+ IconButton(
+ onClick = {
+ springSpecControllerExpandedStatus = !springSpecControllerExpandedStatus
+ }
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.ExpandMore,
+ contentDescription = if (springSpecControllerExpandedStatus) "ExpandLess" else "ExpandMore",
+ modifier = Modifier
+ .rotate(expandedRotation)
+ )
+ }
+ }
+ AnimatedContent(
+ targetState = springSpecControllerExpandedStatus,
+ transitionSpec = { springSpecStudioControllerTransitionSpec },
+
+ ) { status ->
+ if (status) {
+ Column(
+ modifier = Modifier
+ .padding(bottom = 10.dp)
+ ) {
+ HorizontalDivider(thickness = 0.5.dp)
+ Spacer(modifier = Modifier.padding(4.dp))
+ springSpecStudioController()
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/spring/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/index.kt
new file mode 100644
index 0000000000..1f4c7a0b76
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/index.kt
@@ -0,0 +1,143 @@
+package com.baidaidai.animora.components.spring
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardColors
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import com.baidaidai.animora.components.spring.animationStudio.animationStudio
+import com.baidaidai.animora.components.spring.model.springSpecStudioViewModel
+import com.baidaidai.animora.components.spring.components.NecessaryComponents
+import com.baidaidai.animora.components.spring.components.springSpecControllerHost
+import com.baidaidai.animora.components.spring.components.springSpecStudioController
+import com.baidaidai.animora.shared.viewModel.blueStateViewModel
+import dev.jeziellago.compose.markdowntext.MarkdownText
+
+val LocalSpringSpecStudioViewModel = compositionLocalOf{ error("No springSpecStudioViewModel provided") }
+
+/**
+ * Spring 动画工作室的主屏幕容器。
+ *
+ * 这个 Composable 函数构建了整个 Spring 动画工作室的 UI,包括:
+ * - 顶部应用栏([NecessaryComponents.springSpecTopAppBar])。
+ * - 悬浮操作按钮([NecessaryComponents.springSpecFloatActionButton]),用于触发动画。
+ * - 动画预览区域([animationStudio])。
+ * - 参数控制器([springSpecControllerHost]),通过 [CompositionLocalProvider] 注入 ViewModel。
+ * - 显示相关信息的 Markdown 文本。
+ *
+ * @param navController 用于处理导航事件(如返回上一页)的 [NavController]。
+ */
+@Composable
+fun springSpecSceenContainer(
+ navController: NavController
+){
+
+ val blueStateViewModel = viewModel()
+ val springSpecStudioViewModel = viewModel()
+
+ val blueState by blueStateViewModel.blueState.collectAsState()
+
+ val assetsManager = LocalContext.current.assets
+ var inputStream = assetsManager.open("markdown/springSpec.md")
+ var markdownContent = inputStream.bufferedReader().use { it.readText() }
+
+ Scaffold(
+ topBar = {
+ NecessaryComponents.springSpecTopAppBar(
+ content = "SpringSpce Studio"
+ ) {
+ navController.popBackStack()
+ blueStateViewModel.changeBlueState(false)
+ }
+ },
+ floatingActionButton = {
+ NecessaryComponents.springSpecFloatActionButton {
+ blueStateViewModel.changeBlueState(!blueState)
+ }
+ }
+ ){ contentPaddingValues->
+ Column(
+ modifier = Modifier
+ .padding(contentPaddingValues)
+ .scrollable(
+ state = rememberScrollableState { 1f },
+ orientation = Orientation.Vertical
+ )
+ .padding(horizontal = 30.dp)
+ ){
+ Card(
+ colors = CardColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ disabledContainerColor = MaterialTheme.colorScheme.error,
+ disabledContentColor = MaterialTheme.colorScheme.onError
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Box(
+ modifier = Modifier
+ .padding(30.dp)
+ ) {
+ animationStudio(
+ blueState = blueState,
+ springSpecStudioViewModel = springSpecStudioViewModel
+ )
+ }
+ }
+ HorizontalDivider(
+ thickness = 1.dp,
+ modifier = Modifier
+ .padding(
+ top = 10.dp,
+ bottom = 10.dp,
+ start = 5.dp,
+ end = 5.dp
+ )
+ )
+ CompositionLocalProvider(
+ LocalSpringSpecStudioViewModel provides springSpecStudioViewModel
+ ) {
+ springSpecControllerHost()
+ }
+ HorizontalDivider(
+ thickness = 0.1.dp,
+ modifier = Modifier
+ .padding(
+ bottom = 10.dp,
+ start = 5.dp,
+ end = 5.dp
+ )
+ )
+ MarkdownText(
+ markdown = markdownContent,
+ modifier = Modifier
+ .verticalScroll(
+ state = rememberScrollState()
+ )
+ .padding(
+ bottom = 100.dp
+ )
+ )
+ }
+ }
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/spring/model/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/model/index.kt
new file mode 100644
index 0000000000..50ecc3234d
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/model/index.kt
@@ -0,0 +1,39 @@
+package com.baidaidai.animora.components.spring.model
+
+import androidx.compose.animation.core.Spring
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+data class springSpecParameterData(
+ val dampingRatio: Float,
+ val stiffness: Float
+)
+
+class springSpecStudioViewModel: ViewModel(){
+ private val _enableSpring = MutableStateFlow(true)
+ private val _springSpecValue = MutableStateFlow(
+ value = springSpecParameterData(
+ dampingRatio = Spring.DampingRatioLowBouncy,
+ stiffness = Spring.StiffnessLow,
+ )
+ )
+ val enableSpring = _enableSpring.asStateFlow()
+ val springSpecValue = _springSpecValue.asStateFlow()
+
+ fun changeSpringSpecValue(
+ dampingRatio: Float,
+ stiffness: Float
+ ){
+ _springSpecValue.update { oldSpringSpecParameterData ->
+ oldSpringSpecParameterData.copy(
+ dampingRatio = dampingRatio,
+ stiffness = stiffness
+ )
+ }
+ }
+ fun changeEnableSpringStatus(state: Boolean){
+ _enableSpring.update { state }
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/components/spring/model/list/index.kt b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/model/list/index.kt
new file mode 100644
index 0000000000..bce81f4c9c
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/components/spring/model/list/index.kt
@@ -0,0 +1,61 @@
+package com.baidaidai.animora.components.spring.model.list
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.baidaidai.animora.R
+
+data class dampingRatioTabInfo(
+ val featureName:String,
+ val dampingRatioValue: Float
+)
+data class stiffnessTabInfo(
+ val featureName:String,
+ val stiffnessValue: Float
+)
+
+val dampingRatioTabRenderingList = listOf(
+ dampingRatioTabInfo(
+ featureName = "No",
+ dampingRatioValue = 1f
+ ),
+ dampingRatioTabInfo(
+ featureName = "Low",
+ dampingRatioValue = 0.75f
+ ),
+ dampingRatioTabInfo(
+ featureName = "Mid",
+ dampingRatioValue = 0.5f
+ ),
+ dampingRatioTabInfo(
+ featureName = "High",
+ dampingRatioValue = 0.2f
+ )
+)
+
+val stiffnessTabRenderingList = listOf(
+ stiffnessTabInfo(
+ featureName = "Small",
+ stiffnessValue = 50f
+ ),
+ stiffnessTabInfo(
+ featureName = "Low",
+ stiffnessValue = 200f
+ ),
+ stiffnessTabInfo(
+ featureName = "Mid",
+ stiffnessValue = 400f
+ ),
+ stiffnessTabInfo(
+ featureName = "High",
+ stiffnessValue = 1500f
+ ),
+ stiffnessTabInfo(
+ featureName = "Big",
+ stiffnessValue = 10000f
+ )
+)
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/fetchfun/Main.kt b/Animora/app/src/main/java/com/baidaidai/animora/fetchfun/Main.kt
new file mode 100644
index 0000000000..409ef8326e
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/fetchfun/Main.kt
@@ -0,0 +1,66 @@
+package com.baidaidai.fetch
+
+import okhttp3.*
+import okhttp3.RequestBody.Companion.toRequestBody
+
+val clinit = OkHttpClient()
+
+/**
+ * HTTP request methods supported by the fetch function.
+ *
+ * @param names The string representation of the HTTP method.
+ * @author Creater. Bai
+ */
+enum class HttpMethods(val names: String){
+ post("POST"),
+ get("GET"),
+ put("PUT"),
+ delete("DELETE"),
+}
+
+/**
+ * To Build A HttpOptions Class
+ */
+class HTTPOptions(){
+ var methods:String = HttpMethods.get.names
+ var body:RequestBody? = null
+ var headers:Headers = Headers.Builder().build()
+
+ fun methods(param_methods:HttpMethods?){
+ methods = param_methods?.names ?: HttpMethods.get.names
+ }
+ fun head(header:Headers.Builder.()->Unit){
+ val _headers = Headers.Builder()
+ _headers.header()
+ headers = _headers.build()
+ }
+ fun body(bodies:String?){
+ body = bodies?.trimIndent()?.toRequestBody()
+ }
+
+}
+
+suspend fun fetch(url: String,options:HTTPOptions.() -> Unit = {}):String{
+ var responseBody:String
+ val httpOptions = HTTPOptions()
+ httpOptions.options()
+
+ println(httpOptions.headers)
+
+ val request = Request.Builder()
+ .url(url)
+ .headers(httpOptions.headers)
+ .method(
+ method = httpOptions.methods,
+ body = httpOptions.body
+ )
+ .build()
+
+ clinit.newCall(request)
+ .execute()
+ .use {
+ responseBody = it.body?.string() ?: "Body is Void"
+ }
+
+ return responseBody
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/shared/dataClass/AnimationDatas.kt b/Animora/app/src/main/java/com/baidaidai/animora/shared/dataClass/AnimationDatas.kt
new file mode 100644
index 0000000000..3e2886d0f1
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/shared/dataClass/AnimationDatas.kt
@@ -0,0 +1,13 @@
+package com.baidaidai.animora.shared.dataClass
+
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+
+final data class AnimationDatas(
+ val id: Int,
+ val name: String,
+ @StringRes
+ val shortInfo: Int,
+ val details: String,
+ val animationFiles:@Composable (Boolean) -> Unit
+)
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/shared/viewModel/animationDatasViewModel.kt b/Animora/app/src/main/java/com/baidaidai/animora/shared/viewModel/animationDatasViewModel.kt
new file mode 100644
index 0000000000..93368d503b
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/shared/viewModel/animationDatasViewModel.kt
@@ -0,0 +1,20 @@
+package com.baidaidai.animora.shared.viewModel
+
+import androidx.lifecycle.ViewModel
+import com.baidaidai.animora.components.StartScreen.list.model.animationList
+import com.baidaidai.animora.shared.dataClass.AnimationDatas
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class animationDatasViewModel: ViewModel() {
+ private val _selectedAnimation = MutableStateFlow(animationList[0])
+ val selectedAnimation = _selectedAnimation.asStateFlow()
+
+
+ fun changeSelectedAnimation(animationDatas: AnimationDatas) {
+ _selectedAnimation.update {
+ animationDatas
+ }
+ }
+}
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/shared/viewModel/blueStateViewModel.kt b/Animora/app/src/main/java/com/baidaidai/animora/shared/viewModel/blueStateViewModel.kt
new file mode 100644
index 0000000000..9054c5e0c7
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/shared/viewModel/blueStateViewModel.kt
@@ -0,0 +1,14 @@
+package com.baidaidai.animora.shared.viewModel
+
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class blueStateViewModel: ViewModel() {
+ private val _blueState = MutableStateFlow(false)
+ val blueState = _blueState.asStateFlow()
+
+ fun changeBlueState(state: Boolean){
+ _blueState.value = state
+ }
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Color.kt b/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Color.kt
new file mode 100644
index 0000000000..d91bdc56f0
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Color.kt
@@ -0,0 +1,219 @@
+package com.baidaidai.animora.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val primaryLight = Color(0xFF63568F)
+val onPrimaryLight = Color(0xFFFFFFFF)
+val primaryContainerLight = Color(0xFFE8DDFF)
+val onPrimaryContainerLight = Color(0xFF4B3E76)
+val secondaryLight = Color(0xFF615B71)
+val onSecondaryLight = Color(0xFFFFFFFF)
+val secondaryContainerLight = Color(0xFFE7DEF8)
+val onSecondaryContainerLight = Color(0xFF494458)
+val tertiaryLight = Color(0xFF7D5261)
+val onTertiaryLight = Color(0xFFFFFFFF)
+val tertiaryContainerLight = Color(0xFFFFD9E4)
+val onTertiaryContainerLight = Color(0xFF633B49)
+val errorLight = Color(0xFFBA1A1A)
+val onErrorLight = Color(0xFFFFFFFF)
+val errorContainerLight = Color(0xFFFFDAD6)
+val onErrorContainerLight = Color(0xFF93000A)
+val backgroundLight = Color(0xFFFDF7FF)
+val onBackgroundLight = Color(0xFF1C1B20)
+val surfaceLight = Color(0xFFFDF7FF)
+val onSurfaceLight = Color(0xFF1C1B20)
+val surfaceVariantLight = Color(0xFFE6E0EC)
+val onSurfaceVariantLight = Color(0xFF48454E)
+val outlineLight = Color(0xFF79757F)
+val outlineVariantLight = Color(0xFFCAC4CF)
+val scrimLight = Color(0xFF000000)
+val inverseSurfaceLight = Color(0xFF322F35)
+val inverseOnSurfaceLight = Color(0xFFF5EFF7)
+val inversePrimaryLight = Color(0xFFCDBDFF)
+val surfaceDimLight = Color(0xFFDDD8E0)
+val surfaceBrightLight = Color(0xFFFDF7FF)
+val surfaceContainerLowestLight = Color(0xFFFFFFFF)
+val surfaceContainerLowLight = Color(0xFFF7F2FA)
+val surfaceContainerLight = Color(0xFFF2ECF4)
+val surfaceContainerHighLight = Color(0xFFECE6EE)
+val surfaceContainerHighestLight = Color(0xFFE6E1E9)
+
+val primaryLightMediumContrast = Color(0xFF3A2D64)
+val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
+val primaryContainerLightMediumContrast = Color(0xFF72649F)
+val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val secondaryLightMediumContrast = Color(0xFF383347)
+val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
+val secondaryContainerLightMediumContrast = Color(0xFF706A80)
+val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val tertiaryLightMediumContrast = Color(0xFF502B39)
+val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
+val tertiaryContainerLightMediumContrast = Color(0xFF8D6070)
+val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val errorLightMediumContrast = Color(0xFF740006)
+val onErrorLightMediumContrast = Color(0xFFFFFFFF)
+val errorContainerLightMediumContrast = Color(0xFFCF2C27)
+val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
+val backgroundLightMediumContrast = Color(0xFFFDF7FF)
+val onBackgroundLightMediumContrast = Color(0xFF1C1B20)
+val surfaceLightMediumContrast = Color(0xFFFDF7FF)
+val onSurfaceLightMediumContrast = Color(0xFF121016)
+val surfaceVariantLightMediumContrast = Color(0xFFE6E0EC)
+val onSurfaceVariantLightMediumContrast = Color(0xFF38353D)
+val outlineLightMediumContrast = Color(0xFF54515A)
+val outlineVariantLightMediumContrast = Color(0xFF6F6B75)
+val scrimLightMediumContrast = Color(0xFF000000)
+val inverseSurfaceLightMediumContrast = Color(0xFF322F35)
+val inverseOnSurfaceLightMediumContrast = Color(0xFFF5EFF7)
+val inversePrimaryLightMediumContrast = Color(0xFFCDBDFF)
+val surfaceDimLightMediumContrast = Color(0xFFCAC5CD)
+val surfaceBrightLightMediumContrast = Color(0xFFFDF7FF)
+val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
+val surfaceContainerLowLightMediumContrast = Color(0xFFF7F2FA)
+val surfaceContainerLightMediumContrast = Color(0xFFECE6EE)
+val surfaceContainerHighLightMediumContrast = Color(0xFFE0DBE3)
+val surfaceContainerHighestLightMediumContrast = Color(0xFFD5D0D8)
+
+val primaryLightHighContrast = Color(0xFF302259)
+val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
+val primaryContainerLightHighContrast = Color(0xFF4E4078)
+val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val secondaryLightHighContrast = Color(0xFF2E293C)
+val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
+val secondaryContainerLightHighContrast = Color(0xFF4C465B)
+val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val tertiaryLightHighContrast = Color(0xFF44212F)
+val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
+val tertiaryContainerLightHighContrast = Color(0xFF653D4C)
+val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val errorLightHighContrast = Color(0xFF600004)
+val onErrorLightHighContrast = Color(0xFFFFFFFF)
+val errorContainerLightHighContrast = Color(0xFF98000A)
+val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
+val backgroundLightHighContrast = Color(0xFFFDF7FF)
+val onBackgroundLightHighContrast = Color(0xFF1C1B20)
+val surfaceLightHighContrast = Color(0xFFFDF7FF)
+val onSurfaceLightHighContrast = Color(0xFF000000)
+val surfaceVariantLightHighContrast = Color(0xFFE6E0EC)
+val onSurfaceVariantLightHighContrast = Color(0xFF000000)
+val outlineLightHighContrast = Color(0xFF2D2B33)
+val outlineVariantLightHighContrast = Color(0xFF4B4851)
+val scrimLightHighContrast = Color(0xFF000000)
+val inverseSurfaceLightHighContrast = Color(0xFF322F35)
+val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
+val inversePrimaryLightHighContrast = Color(0xFFCDBDFF)
+val surfaceDimLightHighContrast = Color(0xFFBCB7BF)
+val surfaceBrightLightHighContrast = Color(0xFFFDF7FF)
+val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
+val surfaceContainerLowLightHighContrast = Color(0xFFF5EFF7)
+val surfaceContainerLightHighContrast = Color(0xFFE6E1E9)
+val surfaceContainerHighLightHighContrast = Color(0xFFD8D3DA)
+val surfaceContainerHighestLightHighContrast = Color(0xFFCAC5CD)
+
+val primaryDark = Color(0xFFCDBDFF)
+val onPrimaryDark = Color(0xFF34275E)
+val primaryContainerDark = Color(0xFF4B3E76)
+val onPrimaryContainerDark = Color(0xFFE8DDFF)
+val secondaryDark = Color(0xFFCBC3DC)
+val onSecondaryDark = Color(0xFF322E41)
+val secondaryContainerDark = Color(0xFF494458)
+val onSecondaryContainerDark = Color(0xFFE7DEF8)
+val tertiaryDark = Color(0xFFEFB8C9)
+val onTertiaryDark = Color(0xFF492533)
+val tertiaryContainerDark = Color(0xFF633B49)
+val onTertiaryContainerDark = Color(0xFFFFD9E4)
+val errorDark = Color(0xFFFFB4AB)
+val onErrorDark = Color(0xFF690005)
+val errorContainerDark = Color(0xFF93000A)
+val onErrorContainerDark = Color(0xFFFFDAD6)
+val backgroundDark = Color(0xFF141318)
+val onBackgroundDark = Color(0xFFE6E1E9)
+val surfaceDark = Color(0xFF141318)
+val onSurfaceDark = Color(0xFFE6E1E9)
+val surfaceVariantDark = Color(0xFF48454E)
+val onSurfaceVariantDark = Color(0xFFCAC4CF)
+val outlineDark = Color(0xFF938F99)
+val outlineVariantDark = Color(0xFF48454E)
+val scrimDark = Color(0xFF000000)
+val inverseSurfaceDark = Color(0xFFE6E1E9)
+val inverseOnSurfaceDark = Color(0xFF322F35)
+val inversePrimaryDark = Color(0xFF63568F)
+val surfaceDimDark = Color(0xFF141318)
+val surfaceBrightDark = Color(0xFF3A383E)
+val surfaceContainerLowestDark = Color(0xFF0F0D13)
+val surfaceContainerLowDark = Color(0xFF1C1B20)
+val surfaceContainerDark = Color(0xFF211F24)
+val surfaceContainerHighDark = Color(0xFF2B292F)
+val surfaceContainerHighestDark = Color(0xFF36343A)
+
+val primaryDarkMediumContrast = Color(0xFFE2D6FF)
+val onPrimaryDarkMediumContrast = Color(0xFF291B52)
+val primaryContainerDarkMediumContrast = Color(0xFF9788C5)
+val onPrimaryContainerDarkMediumContrast = Color(0xFF000000)
+val secondaryDarkMediumContrast = Color(0xFFE1D8F2)
+val onSecondaryDarkMediumContrast = Color(0xFF272336)
+val secondaryContainerDarkMediumContrast = Color(0xFF948DA5)
+val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
+val tertiaryDarkMediumContrast = Color(0xFFFFD0DE)
+val onTertiaryDarkMediumContrast = Color(0xFF3D1A28)
+val tertiaryContainerDarkMediumContrast = Color(0xFFB48393)
+val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
+val errorDarkMediumContrast = Color(0xFFFFD2CC)
+val onErrorDarkMediumContrast = Color(0xFF540003)
+val errorContainerDarkMediumContrast = Color(0xFFFF5449)
+val onErrorContainerDarkMediumContrast = Color(0xFF000000)
+val backgroundDarkMediumContrast = Color(0xFF141318)
+val onBackgroundDarkMediumContrast = Color(0xFFE6E1E9)
+val surfaceDarkMediumContrast = Color(0xFF141318)
+val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF)
+val surfaceVariantDarkMediumContrast = Color(0xFF48454E)
+val onSurfaceVariantDarkMediumContrast = Color(0xFFE0DAE5)
+val outlineDarkMediumContrast = Color(0xFFB5B0BB)
+val outlineVariantDarkMediumContrast = Color(0xFF938E99)
+val scrimDarkMediumContrast = Color(0xFF000000)
+val inverseSurfaceDarkMediumContrast = Color(0xFFE6E1E9)
+val inverseOnSurfaceDarkMediumContrast = Color(0xFF2B292F)
+val inversePrimaryDarkMediumContrast = Color(0xFF4C3F77)
+val surfaceDimDarkMediumContrast = Color(0xFF141318)
+val surfaceBrightDarkMediumContrast = Color(0xFF46434A)
+val surfaceContainerLowestDarkMediumContrast = Color(0xFF08070B)
+val surfaceContainerLowDarkMediumContrast = Color(0xFF1E1D22)
+val surfaceContainerDarkMediumContrast = Color(0xFF29272D)
+val surfaceContainerHighDarkMediumContrast = Color(0xFF343238)
+val surfaceContainerHighestDarkMediumContrast = Color(0xFF3F3D43)
+
+val primaryDarkHighContrast = Color(0xFFF4EDFF)
+val onPrimaryDarkHighContrast = Color(0xFF000000)
+val primaryContainerDarkHighContrast = Color(0xFFCAB9FA)
+val onPrimaryContainerDarkHighContrast = Color(0xFF0E0034)
+val secondaryDarkHighContrast = Color(0xFFF4EDFF)
+val onSecondaryDarkHighContrast = Color(0xFF000000)
+val secondaryContainerDarkHighContrast = Color(0xFFC7BFD8)
+val onSecondaryContainerDarkHighContrast = Color(0xFF0D081A)
+val tertiaryDarkHighContrast = Color(0xFFFFEBEF)
+val onTertiaryDarkHighContrast = Color(0xFF000000)
+val tertiaryContainerDarkHighContrast = Color(0xFFEAB4C5)
+val onTertiaryContainerDarkHighContrast = Color(0xFF1D020D)
+val errorDarkHighContrast = Color(0xFFFFECE9)
+val onErrorDarkHighContrast = Color(0xFF000000)
+val errorContainerDarkHighContrast = Color(0xFFFFAEA4)
+val onErrorContainerDarkHighContrast = Color(0xFF220001)
+val backgroundDarkHighContrast = Color(0xFF141318)
+val onBackgroundDarkHighContrast = Color(0xFFE6E1E9)
+val surfaceDarkHighContrast = Color(0xFF141318)
+val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
+val surfaceVariantDarkHighContrast = Color(0xFF48454E)
+val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF)
+val outlineDarkHighContrast = Color(0xFFF4EEF9)
+val outlineVariantDarkHighContrast = Color(0xFFC6C0CB)
+val scrimDarkHighContrast = Color(0xFF000000)
+val inverseSurfaceDarkHighContrast = Color(0xFFE6E1E9)
+val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
+val inversePrimaryDarkHighContrast = Color(0xFF4C3F77)
+val surfaceDimDarkHighContrast = Color(0xFF141318)
+val surfaceBrightDarkHighContrast = Color(0xFF524F55)
+val surfaceContainerLowestDarkHighContrast = Color(0xFF000000)
+val surfaceContainerLowDarkHighContrast = Color(0xFF211F24)
+val surfaceContainerDarkHighContrast = Color(0xFF322F35)
+val surfaceContainerHighDarkHighContrast = Color(0xFF3D3A41)
+val surfaceContainerHighestDarkHighContrast = Color(0xFF48464C)
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Theme.kt b/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Theme.kt
new file mode 100644
index 0000000000..d0e1e70b0a
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Theme.kt
@@ -0,0 +1,113 @@
+package com.baidaidai.animora.ui.theme
+
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialExpressiveTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+
+private val lightScheme = lightColorScheme(
+ primary = primaryLight,
+ onPrimary = onPrimaryLight,
+ primaryContainer = primaryContainerLight,
+ onPrimaryContainer = onPrimaryContainerLight,
+ secondary = secondaryLight,
+ onSecondary = onSecondaryLight,
+ secondaryContainer = secondaryContainerLight,
+ onSecondaryContainer = onSecondaryContainerLight,
+ tertiary = tertiaryLight,
+ onTertiary = onTertiaryLight,
+ tertiaryContainer = tertiaryContainerLight,
+ onTertiaryContainer = onTertiaryContainerLight,
+ error = errorLight,
+ onError = onErrorLight,
+ errorContainer = errorContainerLight,
+ onErrorContainer = onErrorContainerLight,
+ background = backgroundLight,
+ onBackground = onBackgroundLight,
+ surface = surfaceLight,
+ onSurface = onSurfaceLight,
+ surfaceVariant = surfaceVariantLight,
+ onSurfaceVariant = onSurfaceVariantLight,
+ outline = outlineLight,
+ outlineVariant = outlineVariantLight,
+ scrim = scrimLight,
+ inverseSurface = inverseSurfaceLight,
+ inverseOnSurface = inverseOnSurfaceLight,
+ inversePrimary = inversePrimaryLight,
+ surfaceDim = surfaceDimLight,
+ surfaceBright = surfaceBrightLight,
+ surfaceContainerLowest = surfaceContainerLowestLight,
+ surfaceContainerLow = surfaceContainerLowLight,
+ surfaceContainer = surfaceContainerLight,
+ surfaceContainerHigh = surfaceContainerHighLight,
+ surfaceContainerHighest = surfaceContainerHighestLight,
+)
+
+
+private val darkScheme = darkColorScheme(
+ primary = primaryDark,
+ onPrimary = onPrimaryDark,
+ primaryContainer = primaryContainerDark,
+ onPrimaryContainer = onPrimaryContainerDark,
+ secondary = secondaryDark,
+ onSecondary = onSecondaryDark,
+ secondaryContainer = secondaryContainerDark,
+ onSecondaryContainer = onSecondaryContainerDark,
+ tertiary = tertiaryDark,
+ onTertiary = onTertiaryDark,
+ tertiaryContainer = tertiaryContainerDark,
+ onTertiaryContainer = onTertiaryContainerDark,
+ error = errorDark,
+ onError = onErrorDark,
+ errorContainer = errorContainerDark,
+ onErrorContainer = onErrorContainerDark,
+ background = backgroundDark,
+ onBackground = onBackgroundDark,
+ surface = surfaceDark,
+ onSurface = onSurfaceDark,
+ surfaceVariant = surfaceVariantDark,
+ onSurfaceVariant = onSurfaceVariantDark,
+ outline = outlineDark,
+ outlineVariant = outlineVariantDark,
+ scrim = scrimDark,
+ inverseSurface = inverseSurfaceDark,
+ inverseOnSurface = inverseOnSurfaceDark,
+ inversePrimary = inversePrimaryDark,
+ surfaceDim = surfaceDimDark,
+ surfaceBright = surfaceBrightDark,
+ surfaceContainerLowest = surfaceContainerLowestDark,
+ surfaceContainerLow = surfaceContainerLowDark,
+ surfaceContainer = surfaceContainerDark,
+ surfaceContainerHigh = surfaceContainerHighDark,
+ surfaceContainerHighest = surfaceContainerHighestDark,
+)
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun TestAppTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+ val colorScheme = when {
+ dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
+ dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
+ darkTheme -> darkScheme
+ else -> lightScheme
+ }
+
+ MaterialExpressiveTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content,
+ shapes = BasicShapes
+ )
+}
\ No newline at end of file
diff --git a/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Type.kt b/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Type.kt
new file mode 100644
index 0000000000..1ee14a4253
--- /dev/null
+++ b/Animora/app/src/main/java/com/baidaidai/animora/ui/theme/Type.kt
@@ -0,0 +1,45 @@
+package com.baidaidai.animora.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Shapes
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+ /* Other default text styles to override
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+ */
+)
+
+val BasicShapes = Shapes(
+ extraSmall = RoundedCornerShape(4.dp),
+ small = RoundedCornerShape(8.dp),
+ medium = RoundedCornerShape(12.dp),
+ large = RoundedCornerShape(16.dp),
+ extraLarge = RoundedCornerShape(30.dp)
+)
\ No newline at end of file
diff --git a/Animora/app/src/main/res/drawable-nodpi/material_symbols_icons_24px.xml b/Animora/app/src/main/res/drawable-nodpi/material_symbols_icons_24px.xml
new file mode 100644
index 0000000000..ade7998427
--- /dev/null
+++ b/Animora/app/src/main/res/drawable-nodpi/material_symbols_icons_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/Animora/app/src/main/res/drawable/add_24px.xml b/Animora/app/src/main/res/drawable/add_24px.xml
new file mode 100644
index 0000000000..2fcde96c96
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/add_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/Animora/app/src/main/res/drawable/animation_icon.xml b/Animora/app/src/main/res/drawable/animation_icon.xml
new file mode 100644
index 0000000000..7aad492d64
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/animation_icon.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/Animora/app/src/main/res/drawable/createrbai.PNG b/Animora/app/src/main/res/drawable/createrbai.PNG
new file mode 100755
index 0000000000..d4a05aa92c
Binary files /dev/null and b/Animora/app/src/main/res/drawable/createrbai.PNG differ
diff --git a/Animora/app/src/main/res/drawable/demo_tree.PNG b/Animora/app/src/main/res/drawable/demo_tree.PNG
new file mode 100644
index 0000000000..04ff7741c3
Binary files /dev/null and b/Animora/app/src/main/res/drawable/demo_tree.PNG differ
diff --git a/Animora/app/src/main/res/drawable/github.xml b/Animora/app/src/main/res/drawable/github.xml
new file mode 100644
index 0000000000..95e93354f6
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/github.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/Animora/app/src/main/res/drawable/ic_launcher_foreground.xml b/Animora/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..c23aae1873
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/Animora/app/src/main/res/drawable/info_24px.xml b/Animora/app/src/main/res/drawable/info_24px.xml
new file mode 100644
index 0000000000..1ece0341d0
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/info_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/Animora/app/src/main/res/drawable/outline_apk_document_24.xml b/Animora/app/src/main/res/drawable/outline_apk_document_24.xml
new file mode 100644
index 0000000000..a1886eefa5
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/outline_apk_document_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Animora/app/src/main/res/drawable/outline_filter_24.xml b/Animora/app/src/main/res/drawable/outline_filter_24.xml
new file mode 100644
index 0000000000..38817d8d75
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/outline_filter_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Animora/app/src/main/res/drawable/outline_question_mark_24.xml b/Animora/app/src/main/res/drawable/outline_question_mark_24.xml
new file mode 100644
index 0000000000..5dce3215fd
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/outline_question_mark_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Animora/app/src/main/res/drawable/twitter.xml b/Animora/app/src/main/res/drawable/twitter.xml
new file mode 100644
index 0000000000..48e27cced5
--- /dev/null
+++ b/Animora/app/src/main/res/drawable/twitter.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/Animora/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Animora/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..ef49c99170
--- /dev/null
+++ b/Animora/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Animora/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Animora/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000..ef49c99170
--- /dev/null
+++ b/Animora/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Animora/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/Animora/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000000..56e7d9d139
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/Animora/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/Animora/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..2c0d24df7f
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/Animora/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/Animora/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000000..27e5428809
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/Animora/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/Animora/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..4d51ae8d25
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/Animora/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/Animora/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..bbc57a5e53
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/Animora/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/Animora/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..8813bfd688
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/Animora/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/Animora/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..435a800bad
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/Animora/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/Animora/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..3c5c1ab7fa
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/Animora/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/Animora/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..e2ffe0d220
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/Animora/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/Animora/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..35da56bc69
Binary files /dev/null and b/Animora/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/Animora/app/src/main/res/values-v31/themes.xml b/Animora/app/src/main/res/values-v31/themes.xml
new file mode 100644
index 0000000000..b64494e70f
--- /dev/null
+++ b/Animora/app/src/main/res/values-v31/themes.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/Animora/app/src/main/res/values/colors.xml b/Animora/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000000..9bbfb435d9
--- /dev/null
+++ b/Animora/app/src/main/res/values/colors.xml
@@ -0,0 +1,11 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+ #D1B6E4
+
\ No newline at end of file
diff --git a/Animora/app/src/main/res/values/strings.xml b/Animora/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..a1bd3df0a1
--- /dev/null
+++ b/Animora/app/src/main/res/values/strings.xml
@@ -0,0 +1,60 @@
+
+ Animora
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata
+
+
+ 这个动画根据 blueState 的布尔值变化,在红色与灰色之间平滑过渡,并实时更新 Box 的背景色。\n\n它是一个最基础、最轻量的 颜色过渡动画(Color Transition Animation),适用于状态变化时快速给用户一个自然的视觉反馈。
+
+
+ 当一个组件的尺寸因内容或状态改变时,这个 Modifier 会自动为其尺寸变化添加平滑的动画过渡效果。\n\n它非常适合用于可展开/折叠的卡片、动态文本块等场景,避免尺寸突变带来的生硬感。
+
+
+ 通过一个布尔值来控制其子内容的出现和消失,并在此过程中应用指定的进入(Enter)和退出(Exit)动画。\n\n你可以用它轻松实现元素的淡入淡出、滑动进入/退出等效果,是控制UI元素显隐的理想选择。
+
+
+ 这是一个专门用于处理透明度(Alpha)的单值动画。它根据状态变化,在两个浮点数值之间平滑过渡,常用于实现组件的淡入淡出效果。\n\n和 animateColorAsState 类似,它也是一个轻量级的“即发即忘”式动画。
+
+
+ 共享元素过渡(Shared Element Transition)可以在两个不同的布局状态之间,为一个或多个共享的组件创建连贯的、魔法般的移动和形变动画。\n\n它极大地提升了用户体验,尤其是在导航场景中(如从列表页到详情页),能帮助用户清晰地感知元素的来龙去脉。
+
+
+ sharedElement 是 Jetpack Compose 中用于实现共享元素过渡的状态驱动动画 API。\n\n它通过 sharedContentState 为不同状态下的 Composable 绑定统一身份,并结合 AnimatedContent 或 AnimatedVisibility 的作用域,在布局结构、尺寸或位置发生变化时,自动生成连贯、自然的过渡动画,使界面切换更具空间连续性与视觉一致性。
+
+
+ 这是一个功能强大的动画协调器。当你的UI需要根据单一状态(如 `Expanded`/`Collapsed`)同时改变多个属性(如颜色、尺寸、圆角)时,`updateTransition` 可以确保所有这些动画同步开始、协同执行。\n\n它是处理复杂、多属性、状态驱动动画的首选方案。
+
+
+ Animatable 是一个功能强大的、基于协程的 API,用于对单个值(如 Float, Color, Dp 等)进行动画。\n\n与animate*AsState 不同,它提供了更精细的控制能力,尤其适合处理手势驱动或需要中断和重启的复杂动画场景。
+
+
+ 通过为 Animatable.animateTo 提供 spring动画规范(AnimationSpec),可以轻松创建出符合物理规律的弹簧动画效果,让组件的运动看起来更自然、生动。
+
+
+ 使用 tween 结合自定义的
+ CubicBezierEasing(三次贝塞尔缓动曲线),可以精确控制动画在时间上的速度变化,实现从“慢-快-慢”到“快-慢”等各种高度定制化的缓动效果。
+
+
+ keyframes(关键帧)动画允许你将一个动画过程分解为多个阶段,并为每个阶段指定目标值和持续时间。\n\n这对于创建复杂的
+ 、多步骤的动画序列非常有用,例如模拟一个物体先加速后减速再停止的运动。
+
+
+ infiniteRepeatable 是一个特殊的 AnimationSpec,它可以将任何其他动画规范(如 tween 或
+ keyframes)包装成一个无限循环的动画。\n\n这对于创建持续的背景效果或加载指示器非常有用。
+
+
+ snap 是一种特殊的 AnimationSpec,它会让值的变化瞬间完成,没有任何过渡动画。\n\n当你需要立即将一个 Composable
+ 更新到新的状态而不需要动画时,可以使用它。
+
+
+ rememberInfiniteTransition 是专门用于创建无限循环动画的 API。\n\n通过它的 animateColor
+ 扩展函数,可以轻松实现一个在多个颜色之间平滑、无限循环过渡的效果,常用于创建闪烁、呼吸或渐变的背景。
+
+
+ rememberInfiniteTransition 的 animateFloat
+ 扩展函数可以创建一个在两个浮点数值之间无限循环的动画。\n\n这非常灵活,可以用来驱动尺寸、透明度、旋转角度、位置偏移等多种视觉属性的持续变化。
+
+ 这是我学习 Jetpack Compose 过程中的最终作品,用于练习 Compose 语法、Android 开发,并记录那些容易被遗忘的动画 API✨\n\n如果这个 App 对你有所帮助,欢迎在 GitHub 点个 ⭐️!\n\n祝你在 Android 的旅程中,始终保持灵感与热爱🚀\n\nCreater. Bai\n2025.12.16
+ https\:\/\/github.com\/Baidaidai-GFWD-origin\/Animora
+ 1.2.8
+
+
\ No newline at end of file
diff --git a/Animora/app/src/main/res/values/themes.xml b/Animora/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000000..7a9729f0d1
--- /dev/null
+++ b/Animora/app/src/main/res/values/themes.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Animora/app/src/main/res/xml/backup_rules.xml b/Animora/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000000..4df9255824
--- /dev/null
+++ b/Animora/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/Animora/app/src/main/res/xml/data_extraction_rules.xml b/Animora/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000000..9ee9997b0b
--- /dev/null
+++ b/Animora/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Animora/app/src/test/java/com/baidaidai/animora/ExampleUnitTest.kt b/Animora/app/src/test/java/com/baidaidai/animora/ExampleUnitTest.kt
new file mode 100644
index 0000000000..5aa951de9c
--- /dev/null
+++ b/Animora/app/src/test/java/com/baidaidai/animora/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.baidaidai.animora
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/Animora/build.gradle.kts b/Animora/build.gradle.kts
new file mode 100644
index 0000000000..952b930665
--- /dev/null
+++ b/Animora/build.gradle.kts
@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.kotlin.compose) apply false
+}
\ No newline at end of file
diff --git a/Animora/gradle.properties b/Animora/gradle.properties
new file mode 100644
index 0000000000..20e2a01520
--- /dev/null
+++ b/Animora/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/Animora/gradle/libs.versions.toml b/Animora/gradle/libs.versions.toml
new file mode 100644
index 0000000000..d5a1560a3b
--- /dev/null
+++ b/Animora/gradle/libs.versions.toml
@@ -0,0 +1,36 @@
+[versions]
+agp = "8.10.0"
+kotlin = "2.0.21"
+coreKtx = "1.10.1"
+junit = "4.13.2"
+junitVersion = "1.1.5"
+espressoCore = "3.5.1"
+lifecycleRuntimeKtx = "2.6.1"
+activityCompose = "1.8.0"
+composeBom = "2024.09.00"
+material3AdaptiveNavigationSuiteAndroid = "1.4.0"
+material = "1.13.0"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+androidx-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-material3-adaptive-navigation-suite-android = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite-android", version.ref = "material3AdaptiveNavigationSuiteAndroid" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+
diff --git a/Animora/gradle/wrapper/gradle-wrapper.jar b/Animora/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..e708b1c023
Binary files /dev/null and b/Animora/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/Animora/gradle/wrapper/gradle-wrapper.properties b/Animora/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..069938b29d
--- /dev/null
+++ b/Animora/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Aug 27 11:59:32 CST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/Animora/gradlew b/Animora/gradlew
new file mode 100755
index 0000000000..4f906e0c81
--- /dev/null
+++ b/Animora/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/Animora/gradlew.bat b/Animora/gradlew.bat
new file mode 100644
index 0000000000..107acd32c4
--- /dev/null
+++ b/Animora/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Animora/publicAsset/images/AnimoraBlur.png b/Animora/publicAsset/images/AnimoraBlur.png
new file mode 100644
index 0000000000..8e51e10769
Binary files /dev/null and b/Animora/publicAsset/images/AnimoraBlur.png differ
diff --git a/Animora/publicAsset/images/AnimoraHome.png b/Animora/publicAsset/images/AnimoraHome.png
new file mode 100644
index 0000000000..f2588b92c5
Binary files /dev/null and b/Animora/publicAsset/images/AnimoraHome.png differ
diff --git a/Animora/publicAsset/images/AnimoraInfo.png b/Animora/publicAsset/images/AnimoraInfo.png
new file mode 100644
index 0000000000..6cef780560
Binary files /dev/null and b/Animora/publicAsset/images/AnimoraInfo.png differ
diff --git a/Animora/publicAsset/images/AnimoraList.png b/Animora/publicAsset/images/AnimoraList.png
new file mode 100644
index 0000000000..6e14e73a86
Binary files /dev/null and b/Animora/publicAsset/images/AnimoraList.png differ
diff --git a/Animora/publicAsset/images/AnimoraSplash.png b/Animora/publicAsset/images/AnimoraSplash.png
new file mode 100644
index 0000000000..2199266d08
Binary files /dev/null and b/Animora/publicAsset/images/AnimoraSplash.png differ
diff --git a/Animora/publicAsset/images/AnimoraStudio.png b/Animora/publicAsset/images/AnimoraStudio.png
new file mode 100644
index 0000000000..0150ef1e51
Binary files /dev/null and b/Animora/publicAsset/images/AnimoraStudio.png differ
diff --git a/Animora/releases/v1.1.5.md b/Animora/releases/v1.1.5.md
new file mode 100644
index 0000000000..31d54be97e
--- /dev/null
+++ b/Animora/releases/v1.1.5.md
@@ -0,0 +1,81 @@
+Welcome to Animora v1.1.6!
+
+This update focuses on introducing new advanced animation examples, enhancing the user experience with UI refinements, and improving overall functionality based on feedback and ongoing development.
+
+欢迎来到 Animora v1.1.6!
+
+此更新专注于引入新的高级动画示例、通过 UI 优化增强用户体验,并根据反馈和持续开发改进整体功能。
+
+---
+
+### What's New in v1.1.6
+
+* **New Animation Demos**:
+ * **Shared Element Transition**: Added a new demo to showcase seamless transitions between screens with shared UI elements, creating a more fluid and connected user experience.
+* **Feature Enhancements & Fixes**:
+ * **Markdown Support**: The animation details screen now supports Markdown for rich text content, allowing for more expressive and detailed explanations.
+ * **`updateTransition` Fix**: Resolved an issue within the `updateTransition` animation family to ensure smoother and more reliable state transitions.
+* **UI & UX Improvements**:
+ * **Colorful Intro Card**: The introduction card has been visually enhanced with more vibrant colors.
+ * **Status Bar Fix**: Addressed and fixed issues related to the status bar display for better consistency across the app.
+ * **Scrollable Content**: Implemented scrolling functionality and added bottom padding to the animation details screen, improving readability and user comfort.
+
+### v1.1.6 更新内容
+
+* **新增动画示例**:
+ * **共享元素过渡 (Shared Element Transition)**: 新增了演示示例,用于展示带有共享 UI 元素的屏幕之间的无缝过渡,创造更流畅、更具连接性的用户体验。
+* **功能增强与修复**:
+ * **Markdown 支持**: 动画详情页现已支持 Markdown 格式的富文本内容,从而可以实现更具表现力和更详细的说明。
+ * **`updateTransition` 修复**: 解决了 `updateTransition` 动画系列中的一个问题,以确保状态转换更平滑、更可靠。
+* **UI 与 UX 优化**:
+ * **多彩介绍卡片**: 对介绍卡片进行了视觉美化,采用了更鲜艳的色彩。
+ * **状态栏修复**: 处理并修复了与状态栏显示相关的问题,以在整个应用中获得更好的一致性。
+ * **内容可滚动**: 为动画详情页实现了滚动功能并增加了底部内边距,改善了可读性和用户舒适度。
+
+---
+
+### Key Features
+
+* **Rich Animation Demos**: Includes implementations of various Jetpack Compose animation APIs, such as:
+ * Basic state animations (`animateColorAsState`, `animateFloatAsState`)
+ * Transition animations (`updateTransition`, `infiniteTransition`)
+ * The flexible `Animatable` API
+ * Content size and visibility animations (`animateContentSize`, `AnimatedVisibility`)
+ * Shared Element Transition
+* **Structured Components**: The application UI is divided into a home screen, an animation list, and an about page, demonstrating basic Compose navigation and layout.
+* **Modern Architecture**: Utilizes `ViewModel` to manage UI state, achieving a separation of concerns.
+* **Clear Project Structure**: Code is made easier to understand and maintain by separating components, shared resources, and business logic.
+
+### 主要功能
+
+* **丰富的动画示例**: 包含多种 Jetpack Compose 动画 API 的实现,例如:
+ * 基础状态动画 (`animateColorAsState`, `animateFloatAsState`)
+ * 过渡动画 (`updateTransition`, `infiniteTransition`)
+ * 灵活的 `Animatable` API
+ * 内容尺寸与可见性动画 (`animateContentSize`, `AnimatedVisibility`)
+ * 共享元素转场动画 (Shared Element Transition)
+* **结构化组件**: 应用界面分为主页、动画列表和关于页面,展示了基本的 Compose 导航和页面布局。
+* **现代化架构**: 采用 `ViewModel` 管理 UI 状态,实现了业务逻辑与 UI 的分离。
+* **清晰的项目结构**: 通过将组件、共享资源和业务逻辑分离,使代码更易于理解和维护。
+
+---
+
+### Known Issues & Future Plans
+
+* As an early release, there may be unknown bugs or performance issues.
+* Some animation effects are still being fine-tuned, and details may change in future releases.
+* Future versions plan to add more complex animation scenarios and interactions.
+
+### 已知问题与未来计划
+
+* 这是一个早期版本,可能存在未知的 Bug 或性能问题。
+* 部分动画效果仍在调整中,细节可能会在后续版本中发生变化。
+* 未来的版本计划增加更多复杂的动画场景和交互。
+
+---
+
+### Thanks
+
+Thank you for your interest and support! We highly welcome any feedback or suggestions via Issues to help us make Animora even better.
+
+感谢您的关注和支持!我们非常欢迎您通过 Issue 提供任何反馈或建议,帮助我们把 Animora 做得更好。
\ No newline at end of file
diff --git a/Animora/releases/v1.2.0.md b/Animora/releases/v1.2.0.md
new file mode 100644
index 0000000000..165dcf394c
--- /dev/null
+++ b/Animora/releases/v1.2.0.md
@@ -0,0 +1,83 @@
+Welcome to Animora v1.2.0!
+
+This major update introduces the Interactive Spring Animation Studio, a powerful new tool for experimenting with spring physics. It also includes significant UI/UX enhancements, such as a new blur effect and broader adoption of Material Design principles, alongside key performance and stability improvements.
+
+欢迎来到 Animora v1.2.0!
+
+本次主版本更新引入了“互动式 Spring 动画工作室”—— 一个用于体验 Spring 物理动画的强大新工具。此外,此版本还包含了重要的 UI/UX 增强功能,例如全新的模糊效果和对 Material Design 设计原则更广泛的采用,同时还进行了一系列关键的性能与稳定性改进。
+
+---
+
+### What's New in v1.2.0
+
+* **New Animation Demos**:
+ * **Interactive Spring Animation Studio**: A new screen dedicated to `SpringSpec` animations. Users can now interactively adjust `stiffness` and `dampingRatio` parameters and see the animation effects in real-time, providing a powerful tool for learning and tuning spring animations.
+* **Feature Enhancements & Fixes**:
+ * **State Persistence**: Replaced `remember` with `rememberSaveable` in lists to better preserve scroll state and user selections across configuration changes.
+ * **Animation Family Fixes**: Resolved issues related to the `SharedTransition` and `updateTransition` animation families, improving their reliability.
+* **UI & UX Improvements**:
+ * **Blur Effect**: Introduced a new blur effect on the list view for a more modern and layered visual experience.
+ * **Material Design Adoption**: Refined the overall UI to better align with Material Design principles, enhancing visual consistency and user experience.
+ * **Splash Screen Update**: The splash screen has been redesigned for a cleaner and more engaging app launch.
+
+### v1.2.0 更新内容
+
+* **新增动画示例**:
+ * **互动式 Spring 动画工作室**: 新增一个专门用于 `SpringSpec` 动画的页面。现在,用户可以交互式地调整 `stiffness` (刚度) 和 `dampingRatio` (阻尼比) 参数,并实时查看动画效果,为学习和调试 Spring 动画提供了强大的工具。
+* **功能增强与修复**:
+ * **状态持久化**: 在列表中使用 `rememberSaveable` 替换了 `remember`,以便在配置变更(如屏幕旋转)后能更好地保持滚动状态和用户选择。
+ * **动画族修复**: 解决了与 `SharedTransition` 和 `updateTransition` 动画族相关的问题,提高了其可靠性。
+* **UI 与 UX 优化**:
+ * **模糊效果**: 在列表视图上引入了新的模糊效果,以提供更现代化和层次分明的视觉体验。
+ * **采用 Material Design**: 对整体 UI 进行了优化,使其更符合 Material Design 的设计原则,增强了视觉一致性和用户体验。
+ * **启动画面更新**: 对应用启动画面进行了重新设计,使其在启动时更加简洁和引人入胜。
+
+---
+
+### Key Features
+
+* **Rich Animation Demos**: Includes implementations of various Jetpack Compose animation APIs, such as:
+ * Basic state animations (`animateColorAsState`, `animateFloatAsState`)
+ * Transition animations (`updateTransition`, `infiniteTransition`)
+ * The flexible `Animatable` API
+ * Content size and visibility animations (`animateContentSize`, `AnimatedVisibility`)
+ * Shared Element Transition
+ * **Interactive Spring Animation Studio**--Beta
+* **Structured Components**: The application UI is divided into a home screen, an animation list, and an about page, demonstrating basic Compose navigation and layout.
+* **Modern Architecture**: Utilizes `ViewModel` to manage UI state, achieving a separation of concerns.
+* **Clear Project Structure**: Code is made easier to understand and maintain by separating components, shared resources, and business logic.
+
+### 主要功能
+
+* **丰富的动画示例**: 包含多种 Jetpack Compose 动画 API 的实现,例如:
+ * 基础状态动画 (`animateColorAsState`, `animateFloatAsState`)
+ * 过渡动画 (`updateTransition`, `infiniteTransition`)
+ * 灵活的 `Animatable` API
+ * 内容尺寸与可见性动画 (`animateContentSize`, `AnimatedVisibility`)
+ * 共享元素转场动画 (Shared Element Transition)
+ * **互动式 Spring 动画工作室**--Beta
+* **结构化组件**: 应用界面分为主页、动画列表和关于页面,展示了基本的 Compose 导航和页面布局。
+* **现代化架构**: 采用 `ViewModel` 管理 UI 状态,实现了业务逻辑与 UI 的分离。
+* **清晰的项目结构**: 通过将组件、共享资源和业务逻辑分离,使代码更易于理解和维护。
+
+---
+
+### Known Issues & Future Plans
+
+* As an early release, there may be unknown bugs or performance issues.
+* Some animation effects are still being fine-tuned, and details may change in future releases.
+* Future versions plan to add more complex animation scenarios and interactions.
+
+### 已知问题与未来计划
+
+* 这是一个早期版本,可能存在未知的 Bug 或性能问题。
+* 部分动画效果仍在调整中,细节可能会在后续版本中发生变化。
+* 未来的版本计划增加更多复杂的动画场景和交互。
+
+---
+
+### Thanks
+
+Thank you for your interest and support! We highly welcome any feedback or suggestions via Issues to help us make Animora even better.
+
+感谢您的关注和支持!我们非常欢迎您通过 Issue 提供任何反馈或建议,帮助我们把 Animora 做得更好。
diff --git a/Animora/releases/v1.2.5.md b/Animora/releases/v1.2.5.md
new file mode 100644
index 0000000000..5fc8a996f7
--- /dev/null
+++ b/Animora/releases/v1.2.5.md
@@ -0,0 +1,51 @@
+Welcome to Animora v1.2.5!
+
+This update introduces a new, more intuitive SpringSpec Controller and includes several UI/UX refinements to enhance your experience. We've also made under-the-hood improvements for better code structure and maintainability.
+
+欢迎来到 Animora v1.2.5!
+
+此更新引入了更直观的全新 SpringSpec 控制器,并包含多项 UI/UX 优化以增强您的体验。我们还对底层代码进行了改进,以实现更佳的代码结构和可维护性。
+
+---
+
+### What's New in v1.2.5
+
+* **New Components**:
+ * **SpringSpec Controller Host**: A new collapsible UI component has been added to the Spring Animation Studio. It provides a cleaner and more organized way to interact with the spring animation parameters.
+* **UI & UX Improvements**:
+ * **Refined Controller UI**: The style of the `DampingRatio` and `Stiffness` toggle buttons has been updated for a more compact and polished look.
+ * **Enhanced Layout**: The overall layout of the Spring Animation Studio has been improved for better readability and usability.
+* **Feature Enhancements & Fixes**:
+ * **Code Refactoring**: The SpringSpec controller and related components have been refactored for better code organization and adherence to best practices, using `CompositionLocalProvider` for more efficient state management.
+
+### v1.2.5 更新内容
+
+* **新组件**:
+ * **SpringSpec 控制器容器**: 在 Spring 动画工作室中添加了一个全新的可折叠 UI 组件。它提供了一种更整洁、更有条理的方式来与 Spring 动画参数进行交互。
+* **UI 与 UX 优化**:
+ * **优化的控制器界面**: 更新了 `DampingRatio` (阻尼比) 和 `Stiffness` (刚度) 切换按钮的样式,使其外观更紧凑、更精致。
+ * **增强的布局**: 改进了 Spring 动画工作室的整体布局,以提高可读性和易用性。
+* **功能增强与修复**:
+ * **代码重构**: 对 SpringSpec 控制器及相关组件进行了重构,以实现更好的代码组织并遵循最佳实践,使用 `CompositionLocalProvider` 进行更高效的状态管理。
+
+---
+
+### Known Issues & Future Plans
+
+* As an early release, there may be unknown bugs or performance issues.
+* Some animation effects are still being fine-tuned, and details may change in future releases.
+* Future versions plan to add more complex animation scenarios and interactions.
+
+### 已知问题与未来计划
+
+* 这是一个早期版本,可能存在未知的 Bug 或性能问题。
+* 部分动画效果仍在调整中,细节可能会在后续版本中发生变化。
+* 未来的版本计划增加更多复杂的动画场景和交互。
+
+---
+
+### Thanks
+
+Thank you for your interest and support! We highly welcome any feedback or suggestions via Issues to help us make Animora even better.
+
+感谢您的关注和支持!我们非常欢迎您通过 Issue 提供任何反馈或建议,帮助我们把 Animora 做得更好。
diff --git a/Animora/releases/v1.2.6.md b/Animora/releases/v1.2.6.md
new file mode 100644
index 0000000000..46d5b2654f
--- /dev/null
+++ b/Animora/releases/v1.2.6.md
@@ -0,0 +1,57 @@
+Welcome to Animora v1.2.6!
+
+This update focuses on improving the transition animations and overall stability of the app. We've also added support for `SharedTransitionLayout` to enhance the animation capabilities.
+
+欢迎来到 Animora v1.2.6!
+
+此更新侧重于改进过渡动画和应用的整体稳定性。我们还添加了对 `SharedTransitionLayout` 的支持,以增强动画功能。
+
+---
+
+### What's New in v1.2.6
+
+* **Feature Enhancements & Fixes**:
+ * **SharedTransitionLayout Support**: Added `SharedTransitionLayout` support to the root component, allowing for more complex and seamless transitions between screens.
+ * **Transition Bug Fixes**: Fixed several bugs related to transitions to improve animation stability and smoothness.
+ * **Composition and Component Fixes**: Resolved various composition and component-related bugs for better performance and reliability.
+ * **Code Refactoring**: Refactored the code to use `LocalComposition` instead of passing parameters, leading to a cleaner and more maintainable codebase.
+ * **SharedBounds Support**: Added support for `sharedBounds` to enhance transition animations.
+ * **Data Rendering Fix**: Fixed a bug related to rendering data.
+* **UI & UX Improvements**:
+ * **Edge-to-Edge Display**: Enabled edge-to-edge display for the info screen, providing a more immersive user experience.
+ * **Linting**: Linted the demo's short info for better code quality.
+
+### v1.2.6 更新内容
+
+* **功能增强与修复**:
+ * **SharedTransitionLayout 支持**: 为根组件添加了 `SharedTransitionLayout` 支持,从而可以实现更复杂、更无缝的屏幕过渡效果。
+ * **过渡动画 Bug 修复**: 修复了多个与过渡相关的 Bug,以提高动画的稳定性和流畅度。
+ * **Composition 和组件修复**: 解决了各种与 Composition 和组件相关的 Bug,以提高性能和可靠性。
+ * **代码重构**: 重构了代码,使用 `LocalComposition` 代替参数传递,使代码库更简洁、更易于维护。
+ * **SharedBounds 支持**: 添加了对 `sharedBounds` 的支持,以增强过渡动画效果。
+ * **数据渲染修复**: 修复了与渲染数据相关的 Bug。
+* **UI 与 UX 优化**:
+ * **沉浸式显示**: 为信息屏幕启用了沉浸式显示,提供了更具沉浸感的用户体验。
+ * **代码规范**: 对演示的简介部分进行了代码规范。
+
+---
+
+### Known Issues & Future Plans
+
+* As an early release, there may be unknown bugs or performance issues.
+* Some animation effects are still being fine-tuned, and details may change in future releases.
+* Future versions plan to add more complex animation scenarios and interactions.
+
+### 已知问题与未来计划
+
+* 这是一个早期版本,可能存在未知的 Bug 或性能问题。
+* 部分动画效果仍在调整中,细节可能会在后续版本中发生变化。
+* 未来的版本计划增加更多复杂的动画场景和交互。
+
+---
+
+### Thanks
+
+Thank you for your interest and support! We highly welcome any feedback or suggestions via Issues to help us make Animora even better.
+
+感谢您的关注和支持!我们非常欢迎您通过 Issue 提供任何反馈或建议,帮助我们把 Animora 做得更好。
diff --git a/Animora/releases/v1.2.7.md b/Animora/releases/v1.2.7.md
new file mode 100644
index 0000000000..e625c54d9f
--- /dev/null
+++ b/Animora/releases/v1.2.7.md
@@ -0,0 +1,55 @@
+Welcome to Animora v1.2.7!
+
+This is a maintenance release focused on improving code quality and documentation, making the project easier to understand and contribute to.
+
+欢迎来到 Animora v1.2.7!
+
+这是一个维护版本,专注于提高代码质量和文档,使项目更易于理解和贡献。
+
+---
+
+### What's New in v1.2.7
+
+* **Documentation**:
+ * Added comprehensive KDoc comments to a wide range of components to improve code readability and maintainability. Key documented areas include:
+ * The entire **Spring Animation Studio** feature (`index`, `animationStudio`, `components`).
+ * **Home Screen** components (`homeScreenComtainer`, `introduceCard`, `onlySpringSpce`).
+ * The main **Start Screen** container.
+ * Various animation demos and UI components from previous updates.
+* **Code Refinements**:
+ * Removed unused preview functions and library imports.
+ * Minor code style adjustments for better consistency.
+
+### v1.2.7 更新内容
+
+* **文档**:
+ * **全面的 KDoc 注释**: 为大量组件添加了详尽的 KDoc 注释,以提高代码可读性和可维护性。主要添加注释的模块包括:
+ * 整个 **Spring 动画工作室** 功能(`index`, `animationStudio`, `components`)。
+ * **主页屏幕** 组件(`homeScreenComtainer`, `introduceCard`, `onlySpringSpce`)。
+ * 主 **启动屏幕** 容器。
+ * 以及先前更新中的多个动画演示和 UI 组件。
+* **代码优化**:
+ * 移除了未使用的预览函数和库导入。
+ * 进行了微小的代码样式调整以提高一致性。
+
+---
+
+### Known Issues & Future Plans
+
+* As an early release, there may be unknown bugs or performance issues.
+* Some animation effects are still being fine-tuned, and details may change in future releases.
+* Future versions plan to add more complex animation scenarios and interactions.
+
+### 已知问题与未来计划
+
+* 这是一个早期版本,可能存在未知的 Bug 或性能问题。
+* 部分动画效果仍在调整中,细节可能会在后续版本中发生变化。
+* 未来的版本计划增加更多复杂的动画场景和交互。
+
+---
+
+### Thanks
+
+Thank you for your interest and support! We highly welcome any feedback or suggestions via Issues to help us make Animora even better.
+
+感谢您的关注和支持!我们非常欢迎您通过 Issue 提供任何反馈或建议,帮助我们把 Animora 做得更好。
diff --git a/Animora/releases/v1.2.8.md b/Animora/releases/v1.2.8.md
new file mode 100644
index 0000000000..c71c54b026
--- /dev/null
+++ b/Animora/releases/v1.2.8.md
@@ -0,0 +1,53 @@
+Welcome to Animora v1.2.8!
+
+This is a maintenance release focused on improving test coverage and accessibility.
+
+欢迎来到 Animora v1.2.8!
+
+这是一个维护版本,专注于提高测试覆盖率和无障碍访问性。
+
+---
+
+### What's New in v1.2.8
+
+* **Testing**:
+ * Added the first unit test for list view navigation. (0c9ec28)
+ * Added the first Android Test for list view navigation. (15128ec)
+ * Added a new Android Test for navigating to the info activity and back. (8cc5360)
+* **Improvements**:
+ * Added `contentDescription` to the home top app bar for better accessibility. (b335791)
+* **Build**:
+ * Added new test dependencies to the build configuration. (3ab6bbe)
+
+### v1.2.8 更新内容
+
+* **测试**:
+ * 为列表视图导航添加了首个单元测试。 (0c9ec28)
+ * 为列表视图导航添加了首个 Android 测试。 (15128ec)
+ * 新增了“导航到信息页再返回”的 Android 测试。 (8cc5360)
+* **优化**:
+ * 为主页顶部应用栏添加了 `contentDescription`,以改善无障碍访问性。 (b335791)
+* **构建**:
+ * 在构建配置中添加了新的测试依赖。 (3ab6bbe)
+
+---
+
+### Known Issues & Future Plans
+
+* As an early release, there may be unknown bugs or performance issues.
+* Some animation effects are still being fine-tuned, and details may change in future releases.
+* Future versions plan to add more complex animation scenarios and interactions.
+
+### 已知问题与未来计划
+
+* 这是一个早期版本,可能存在未知的 Bug 或性能问题。
+* 部分动画效果仍在调整中,细节可能会在后续版本中发生变化。
+* 未来的版本计划增加更多复杂的动画场景和交互。
+
+---
+
+### Thanks
+
+Thank you for your interest and support! We highly welcome any feedback or suggestions via Issues to help us make Animora even better.
+
+感谢您的关注和支持!我们非常欢迎您通过 Issue 提供任何反馈或建议,帮助我们把 Animora 做得更好。
diff --git a/Animora/settings.gradle.kts b/Animora/settings.gradle.kts
new file mode 100644
index 0000000000..01b99c4705
--- /dev/null
+++ b/Animora/settings.gradle.kts
@@ -0,0 +1,27 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ maven {
+ url = uri("https://jitpack.io")
+ }
+ }
+}
+
+rootProject.name = "Animora"
+include(":app")
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c9a8c24faf..7c3f78ebb3 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,8 @@ project from Android Studio following the steps
|
A compose implementation of the Reply material study, an email client app that focuses on adaptive design for mobile, tablets and foldables. It also showcases brand new Material design 3 theming, dynamic colors and navigation components.
• Medium complexity
• Adaptive UI for phones, tablet and desktops
• Foldable support
• Material 3 theming & Components
• Dynamic colors and Light/Dark theme support
**[> Browse](Reply/)**
|
|
| | |
|
A sample sleep tracker app, showcasing how to create custom layouts and graphics in Compose
• Custom Layouts
• Graphs with Paths
**[> Browse](JetLagged/)**
|
|
+| | |
+|
An interactive Jetpack Compose animation showcase
• Animation demos
• Real-time Spring tuning
• Modular architecture
• Comprehensive animation APIs
• Beginner-friendly FS_Explain.md
**[> Browse](Animora/)**
|
|
🧬 Additional samples
------------
diff --git a/readme/Animora.png b/readme/Animora.png
new file mode 100644
index 0000000000..81ec0fed89
Binary files /dev/null and b/readme/Animora.png differ
diff --git a/readme/screenshots/AnimoraStudio.png b/readme/screenshots/AnimoraStudio.png
new file mode 100644
index 0000000000..0150ef1e51
Binary files /dev/null and b/readme/screenshots/AnimoraStudio.png differ