├── .github
├── FUNDING.yml
└── dependabot.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.MD
├── assests
└── screenshots
│ ├── 1.png
│ ├── 10.png
│ ├── 11.png
│ ├── 12.png
│ ├── 13.png
│ ├── 14.png
│ ├── 15.png
│ ├── 16.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 40.png
│ ├── 41.png
│ ├── 42.png
│ ├── 43.png
│ ├── 44.png
│ ├── 45.png
│ ├── 46.png
│ ├── 47.png
│ ├── 48.png
│ ├── 49.png
│ ├── 5.png
│ ├── 50.png
│ ├── 51.png
│ ├── 52.png
│ ├── 53.png
│ ├── 54.png
│ ├── 55.png
│ ├── 56.png
│ ├── 57.png
│ ├── 58.png
│ ├── 59.png
│ ├── 6.png
│ ├── 60.png
│ ├── 61.png
│ ├── 62.png
│ ├── 7.png
│ ├── 8.png
│ ├── 9.png
│ ├── poster.png
│ └── poster_crypto.png
├── build.gradle.kts
├── composeApp
├── build.gradle.kts
└── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── org
│ │ └── company
│ │ └── app
│ │ ├── App.android.kt
│ │ └── theme
│ │ └── Theme.android.kt
│ ├── commonMain
│ ├── composeResources
│ │ ├── drawable
│ │ │ ├── ic_cyclone.xml
│ │ │ ├── ic_dark_mode.xml
│ │ │ ├── ic_light_mode.xml
│ │ │ ├── ic_rotate_right.xml
│ │ │ ├── thumbs_down.png
│ │ │ └── thumbs_up.png
│ │ ├── font
│ │ │ ├── IndieFlower-Regular.ttf
│ │ │ └── PoetsenOne-Regular.ttf
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── org
│ │ └── company
│ │ └── app
│ │ ├── App.kt
│ │ ├── data
│ │ ├── remote
│ │ │ └── CryptoClient.kt
│ │ └── repository
│ │ │ └── CryptoApi.kt
│ │ ├── di
│ │ └── appModule.kt
│ │ ├── domain
│ │ ├── model
│ │ │ ├── categories
│ │ │ │ └── NewsCategoriesItem.kt
│ │ │ ├── crypto
│ │ │ │ ├── Data.kt
│ │ │ │ ├── LatestListing.kt
│ │ │ │ ├── Platform.kt
│ │ │ │ ├── Quote.kt
│ │ │ │ ├── Status.kt
│ │ │ │ └── USD.kt
│ │ │ └── news
│ │ │ │ ├── Data.kt
│ │ │ │ ├── NewsList.kt
│ │ │ │ ├── RateLimit.kt
│ │ │ │ └── SourceInfo.kt
│ │ ├── repository
│ │ │ └── Repository.kt
│ │ └── usecase
│ │ │ └── ResultState.kt
│ │ ├── presentation
│ │ ├── ui
│ │ │ ├── components
│ │ │ │ ├── ChartImage.kt
│ │ │ │ ├── CryptoChart.kt
│ │ │ │ ├── CryptoList.kt
│ │ │ │ ├── CurrencyImage.kt
│ │ │ │ ├── ErrorBox.kt
│ │ │ │ ├── LoadingBox.kt
│ │ │ │ ├── MarketRow.kt
│ │ │ │ ├── NewsDetail.kt
│ │ │ │ ├── PromotionalCard.kt
│ │ │ │ └── SuggestionMessage.kt
│ │ │ ├── navigation
│ │ │ │ ├── rails
│ │ │ │ │ ├── items
│ │ │ │ │ │ └── NavigationItem.kt
│ │ │ │ │ └── navbar
│ │ │ │ │ │ └── NavigationSideBar.kt
│ │ │ │ └── tab
│ │ │ │ │ ├── analytics
│ │ │ │ │ └── Analytics.kt
│ │ │ │ │ ├── home
│ │ │ │ │ └── Home.kt
│ │ │ │ │ ├── news
│ │ │ │ │ └── News.kt
│ │ │ │ │ └── profile
│ │ │ │ │ └── Profile.kt
│ │ │ └── screens
│ │ │ │ ├── analytics
│ │ │ │ └── AnalyticScreen.kt
│ │ │ │ ├── detail
│ │ │ │ └── DetailScreen.kt
│ │ │ │ ├── home
│ │ │ │ ├── HomeContent.kt
│ │ │ │ └── HomeScreen.kt
│ │ │ │ ├── news
│ │ │ │ └── NewsScreen.kt
│ │ │ │ └── profile
│ │ │ │ └── ProfileScreen.kt
│ │ └── viewmodel
│ │ │ └── MainViewModel.kt
│ │ ├── theme
│ │ ├── Color.kt
│ │ └── Theme.kt
│ │ └── utils
│ │ ├── Constant.kt
│ │ ├── formatMarketCap.kt
│ │ └── formateTimeStamp.kt
│ ├── commonTest
│ └── kotlin
│ │ └── org
│ │ └── company
│ │ └── app
│ │ └── ComposeTest.kt
│ ├── iosMain
│ └── kotlin
│ │ ├── main.kt
│ │ └── org
│ │ └── company
│ │ └── app
│ │ ├── App.ios.kt
│ │ └── theme
│ │ └── Theme.ios.kt
│ ├── jsMain
│ ├── kotlin
│ │ ├── main.kt
│ │ └── org
│ │ │ └── company
│ │ │ └── app
│ │ │ ├── App.js.kt
│ │ │ └── theme
│ │ │ └── Theme.js.kt
│ └── resources
│ │ └── index.html
│ └── jvmMain
│ └── kotlin
│ ├── main.kt
│ └── org
│ └── company
│ └── app
│ ├── App.jvm.kt
│ └── theme
│ └── Theme.jvm.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── iosApp
├── iosApp.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── iosApp
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── iosApp.swift
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: KhubaibKhan4
4 | patreon: MuhammadKhubaibImtiaz
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: muhammadkhubaibimtiaz
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: khubaibkhan
14 | custom: https://paypal.me/18.bscs
15 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "github-actions" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.iml
3 | .gradle
4 | .idea
5 | .kotlin
6 | .DS_Store
7 | build
8 | */build
9 | captures
10 | .externalNativeBuild
11 | .cxx
12 | local.properties
13 | xcuserdata/
14 | Pods/
15 | *.jks
16 | *yarn.lock
17 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | Email: 18.bscs.803@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # Crypto KMP
2 | 
3 |
4 | ## Overview
5 | Crypto-KMP is a Kotlin Multiplatform (KMP) project aimed at providing cryptocurrency-related functionalities across Android, iOS, Web, and Desktop platforms with shared code. This documentation provides an overview of the project structure, features, and usage instructions.
6 |
7 | ## 🚀 Future Plans
8 | - TV and Wear OS Versions
9 | - Real-time Tracking
10 |
11 | ## 🌟 Contributions
12 | If you wanna contribute, Please make sure to add new features & Then make a PR.Feel free to contribute to the project and stay tuned for more exciting updates!
13 |
14 | # Open To Work
15 | Do you wanna Convert your thoughts into Physicall & Successfull Project Then I'm here for you. I'm open to work, available for Freelance or Remote Work Opportunities. Feel free to reach me out on Email.
16 |
17 | ## 🤝 Connect with Me
18 |
19 | Let's chat about potential projects, job opportunities, or any other collaboration! Feel free to connect with me through the following channels:
20 |
21 | [](https://www.linkedin.com/in/khubaibkhandev)
22 | [](https://twitter.com/codespacepro)
23 | [](mailto:18.bscs.803@gmail.com)
24 |
25 | ## 💰 You can help me by Donating
26 | [](https://buymeacoffee.com/khubaibkhan) [](https://paypal.me/18.bscs) [](https://patreon.com/MuhammadKhubaibImtiaz) [](https://ko-fi.com/muhammadkhubaibimtiaz)
27 |
28 | # Tech Stack Highlights
29 |
30 | - **Kotlin Multiplatform**: `2.0.0`
31 | - **AGP (Android Gradle Plugin)**: `8.2.2`
32 | - **Compose**: `1.7.1`
33 | - `androidx-appcompat`: `1.6.1`
34 | - `androidx-activityCompose`: `1.9.3`
35 | - `ui-testing`: `1.7.6`
36 | - `compose-uitooling`: `1.6.7`
37 | - `composeImageLoader`: `1.7.1`
38 | - `composeIcons`: `1.1.0`
39 | - **Core Libraries**: `12.1.0`
40 | - **Kotlinx Libraries**:
41 | - `kotlinx-coroutines`: `1.9.0-RC`
42 | - `kotlinx-serialization`: `1.6.3`
43 | - `kotlinx-datetime`: `0.5.0`
44 | - **Networking**:
45 | - `coilNetwork`: `3.0.0-alpha01`
46 | - `ktor`: `2.3.12`
47 | - **Dependency Injection**:
48 | - `koin`: `3.6.0-Beta4`
49 | - **UI/UX**:
50 | - `compose-uitooling`: `1.6.3`
51 | - `composeIcons`: `1.1.0`
52 | - **Logging**:
53 | - `napier`: `2.7.1`
54 | - **Database/Storage**:
55 | - `sql-delight`: `2.0.2`
56 | - **JSON Parsing**:
57 | - `json`: `1.6.3`
58 | - **Build Configurations**:
59 | - `buildConfig`: `4.1.1`
60 | - **Cross-Platform Image Loading**:
61 | - `kamelImage`: `0.9.4`
62 | - **Size Measurement**:
63 | - `size`: `0.4.1`
64 | - **Voyager Navigation**: `1.1.0-Beta05`
65 | - **Other Utilities**:
66 | - `official-viewModel`: `2.8.4`
67 |
68 | ## Android Screen Shots
69 |
70 | |  |  |  |
71 | | --- | --- | --- |
72 | |  |  |  |
73 | | --- | --- | --- |
74 | |  |  |  |
75 | | --- | --- | --- |
76 | |  |  |  |
77 | | --- | --- | --- |
78 |  |  |  |
79 | | --- | --- | --- |
80 | |  |
81 |
82 | ## iOS Screen Shots
83 |
84 | |  |  |  |
85 | | --- | --- | --- |
86 | |  |  |  |
87 | | --- | --- | --- |
88 | |  |  | |
89 | | --- | --- | --- |
90 |
91 | ## Desktop Screen Shots
92 |
93 | |  |
94 | | --- |
95 | |  |
96 | |  |
97 | |  |
98 | |  |
99 | |  |
100 | |  |
101 | |  |
102 | |  |
103 | |  |
104 | |  |
105 | |  |
106 | |  |
107 | |  |
108 | |  |
109 |
110 | ## Star History
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | **Stargazers**
121 |
122 | [](https://github.com/KhubaibKhan4/Crypto-KMP/stargazers)
123 |
124 | **Forkers**
125 |
126 | [](https://github.com/KhubaibKhan4/Crypto-KMP/network/members)
127 |
128 | ## Before running!
129 | - check your system with [KDoctor](https://github.com/Kotlin/kdoctor)
130 | - install JDK 17 or higher on your machine
131 | - add `local.properties` file to the project root and set a path to Android SDK there
132 |
133 | ### Android
134 | To run the application on android device/emulator:
135 | - open project in Android Studio and run imported android run configuration
136 |
137 | To build the application bundle:
138 | - run `./gradlew :composeApp:assembleDebug`
139 | - find `.apk` file in `composeApp/build/outputs/apk/debug/composeApp-debug.apk`
140 | Run android simulator UI tests: `./gradlew :composeApp:pixel5Check`
141 |
142 | ### Desktop
143 | Run the desktop application: `./gradlew :composeApp:run`
144 | Run desktop UI tests: `./gradlew :composeApp:jvmTest`
145 |
146 | ### iOS
147 | To run the application on iPhone device/simulator:
148 | - Open `iosApp/iosApp.xcproject` in Xcode and run standard configuration
149 | - Or use [Kotlin Multiplatform Mobile plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile) for Android Studio
150 | Run iOS simulator UI tests: `./gradlew :composeApp:iosSimulatorArm64Test`
151 |
152 | ### Experimental Browser (JS)
153 | Run the browser application: `./gradlew :composeApp:jsBrowserDevelopmentRun --continue`
154 | Run browser UI tests: `./gradlew :composeApp:jsBrowserTest`
155 |
156 | ## Community Resources
157 | Join our community to connect with other users and contributors:
158 |
159 | - **Documentation**: Find more information and resources in our [documentation](https://github.com/KhubaibKhan4/Youtube-Clone-KMP/wiki).
160 | - **Forums**: Ask questions and discuss ideas in our [community forums](https://github.com/KhubaibKhan4/Youtube-Clone-KMP/discussions).
161 | - **Chat**: Join our community chat on [Slack](https://join.slack.com/t/kotlinmultipl-gr51340/shared_invite/zt-2go24sz06-_lyxM2arRifMqwAPN2EzoA) to chat with other users and contributors.
162 |
163 | ## Acknowledgments
164 | We'd like to thank the following individuals and organizations for their contributions to the YouTube Clone project:
165 |
166 | - No Contributor Yet.
167 | - Checkout Other cool projects on my profile as well.
168 |
--------------------------------------------------------------------------------
/assests/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/1.png
--------------------------------------------------------------------------------
/assests/screenshots/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/10.png
--------------------------------------------------------------------------------
/assests/screenshots/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/11.png
--------------------------------------------------------------------------------
/assests/screenshots/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/12.png
--------------------------------------------------------------------------------
/assests/screenshots/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/13.png
--------------------------------------------------------------------------------
/assests/screenshots/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/14.png
--------------------------------------------------------------------------------
/assests/screenshots/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/15.png
--------------------------------------------------------------------------------
/assests/screenshots/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/16.png
--------------------------------------------------------------------------------
/assests/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/2.png
--------------------------------------------------------------------------------
/assests/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/3.png
--------------------------------------------------------------------------------
/assests/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/4.png
--------------------------------------------------------------------------------
/assests/screenshots/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/40.png
--------------------------------------------------------------------------------
/assests/screenshots/41.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/41.png
--------------------------------------------------------------------------------
/assests/screenshots/42.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/42.png
--------------------------------------------------------------------------------
/assests/screenshots/43.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/43.png
--------------------------------------------------------------------------------
/assests/screenshots/44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/44.png
--------------------------------------------------------------------------------
/assests/screenshots/45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/45.png
--------------------------------------------------------------------------------
/assests/screenshots/46.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/46.png
--------------------------------------------------------------------------------
/assests/screenshots/47.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/47.png
--------------------------------------------------------------------------------
/assests/screenshots/48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/48.png
--------------------------------------------------------------------------------
/assests/screenshots/49.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/49.png
--------------------------------------------------------------------------------
/assests/screenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/5.png
--------------------------------------------------------------------------------
/assests/screenshots/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/50.png
--------------------------------------------------------------------------------
/assests/screenshots/51.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/51.png
--------------------------------------------------------------------------------
/assests/screenshots/52.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/52.png
--------------------------------------------------------------------------------
/assests/screenshots/53.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/53.png
--------------------------------------------------------------------------------
/assests/screenshots/54.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/54.png
--------------------------------------------------------------------------------
/assests/screenshots/55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/55.png
--------------------------------------------------------------------------------
/assests/screenshots/56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/56.png
--------------------------------------------------------------------------------
/assests/screenshots/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/57.png
--------------------------------------------------------------------------------
/assests/screenshots/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/58.png
--------------------------------------------------------------------------------
/assests/screenshots/59.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/59.png
--------------------------------------------------------------------------------
/assests/screenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/6.png
--------------------------------------------------------------------------------
/assests/screenshots/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/60.png
--------------------------------------------------------------------------------
/assests/screenshots/61.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/61.png
--------------------------------------------------------------------------------
/assests/screenshots/62.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/62.png
--------------------------------------------------------------------------------
/assests/screenshots/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/7.png
--------------------------------------------------------------------------------
/assests/screenshots/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/8.png
--------------------------------------------------------------------------------
/assests/screenshots/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/9.png
--------------------------------------------------------------------------------
/assests/screenshots/poster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/poster.png
--------------------------------------------------------------------------------
/assests/screenshots/poster_crypto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/assests/screenshots/poster_crypto.png
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.multiplatform).apply(false)
3 | alias(libs.plugins.compose).apply(false)
4 | alias(libs.plugins.android.application).apply(false)
5 | alias(libs.plugins.buildConfig).apply(false)
6 | alias(libs.plugins.kotlinx.serialization).apply(false)
7 | alias(libs.plugins.sqlDelight).apply(false)
8 | alias(libs.plugins.compose.compiler).apply(false)
9 | }
10 |
--------------------------------------------------------------------------------
/composeApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.compose.ExperimentalComposeLibrary
2 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat
3 | import com.android.build.api.dsl.ManagedVirtualDevice
4 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
5 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
6 |
7 | plugins {
8 | alias(libs.plugins.multiplatform)
9 | alias(libs.plugins.compose)
10 | alias(libs.plugins.android.application)
11 | alias(libs.plugins.buildConfig)
12 | alias(libs.plugins.kotlinx.serialization)
13 | alias(libs.plugins.sqlDelight)
14 | alias(libs.plugins.compose.compiler)
15 | }
16 |
17 | kotlin {
18 | androidTarget {
19 | compilations.all {
20 | kotlinOptions {
21 | jvmTarget = "${JavaVersion.VERSION_1_8}"
22 | freeCompilerArgs += "-Xjdk-release=${JavaVersion.VERSION_1_8}"
23 | }
24 | }
25 | //https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-test.html
26 | @OptIn(ExperimentalKotlinGradlePluginApi::class)
27 | instrumentedTestVariant {
28 | sourceSetTree.set(KotlinSourceSetTree.test)
29 | dependencies {
30 | debugImplementation(libs.androidx.testManifest)
31 | implementation(libs.androidx.junit4)
32 | }
33 | }
34 | }
35 |
36 | jvm()
37 |
38 | js {
39 | browser()
40 | binaries.executable()
41 | }
42 |
43 | listOf(
44 | iosX64(),
45 | iosArm64(),
46 | iosSimulatorArm64()
47 | ).forEach {
48 | it.binaries.framework {
49 | baseName = "ComposeApp"
50 | isStatic = true
51 | }
52 | }
53 |
54 | sourceSets {
55 | all {
56 | languageSettings {
57 | optIn("org.jetbrains.compose.resources.ExperimentalResourceApi")
58 | }
59 | }
60 | commonMain.dependencies {
61 | implementation(compose.runtime)
62 | implementation(compose.foundation)
63 | implementation(compose.material3)
64 | implementation(compose.material)
65 | implementation(compose.components.resources)
66 | implementation(compose.components.uiToolingPreview)
67 | implementation(compose.materialIconsExtended)
68 | implementation(libs.voyager.navigator)
69 | implementation(libs.tab.navigator)
70 | implementation(libs.napier)
71 | implementation(libs.kotlinx.coroutines.core)
72 | implementation(libs.lifecycle.viewmodel.compose)
73 | implementation(libs.ktor.core)
74 | implementation(libs.ktor.client.content.negociation)
75 | implementation(libs.ktor.client.serialization.json)
76 | implementation(libs.ktor.client.logging)
77 | implementation(libs.kotlinx.serialization.json)
78 | implementation(libs.kotlinx.datetime)
79 | implementation(libs.screen.size)
80 | implementation(project.dependencies.platform("io.insert-koin:koin-bom:4.0.0"))
81 | implementation(libs.insert.koin.koin.core)
82 | implementation(libs.koin.compose)
83 | implementation(libs.koin.annotations)
84 | implementation(libs.kamel.image)
85 | implementation(libs.kamel.image.default)
86 | implementation(libs.chart)
87 | implementation(libs.bignum)
88 | implementation(libs.ktor.core)
89 | implementation(libs.coil.compose.core)
90 | implementation(libs.coil.compose)
91 | implementation(libs.coil.mp)
92 | implementation(libs.coil.network.ktor)
93 | implementation(libs.alert.kmp)
94 | }
95 |
96 | commonTest.dependencies {
97 | implementation(kotlin("test"))
98 | @OptIn(ExperimentalComposeLibrary::class)
99 | implementation(compose.uiTest)
100 | implementation(libs.kotlinx.coroutines.test)
101 | }
102 |
103 | androidMain.dependencies {
104 | implementation(compose.uiTooling)
105 | implementation(libs.androidx.activityCompose)
106 | implementation(libs.kotlinx.coroutines.android)
107 | implementation(libs.ktor.client.okhttp)
108 | implementation(libs.sqlDelight.driver.android)
109 | implementation(project.dependencies.platform("io.insert-koin:koin-bom:4.0.0"))
110 | implementation(libs.io.insert.koin.koin.core)
111 | implementation(libs.koin.android)
112 | implementation(libs.koin.annotations)
113 | }
114 |
115 | jvmMain.dependencies {
116 | implementation(compose.desktop.currentOs)
117 | implementation(libs.kotlinx.coroutines.swing)
118 | implementation(libs.ktor.client.okhttp)
119 | implementation(libs.sqlDelight.driver.sqlite)
120 | }
121 |
122 | jsMain.dependencies {
123 | implementation(compose.html.core)
124 | implementation(libs.ktor.client.js)
125 | implementation(libs.sqlDelight.driver.js)
126 | }
127 |
128 | iosMain.dependencies {
129 | implementation(libs.ktor.client.darwin)
130 | implementation(libs.sqlDelight.driver.native)
131 | }
132 |
133 | }
134 | }
135 |
136 | android {
137 | namespace = "org.company.app"
138 | compileSdk = 34
139 |
140 | defaultConfig {
141 | minSdk = 24
142 | targetSdk = 34
143 |
144 | applicationId = "org.company.app.androidApp"
145 | versionCode = 1
146 | versionName = "1.0.0"
147 |
148 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
149 | }
150 | sourceSets["main"].apply {
151 | manifest.srcFile("src/androidMain/AndroidManifest.xml")
152 | res.srcDirs("src/androidMain/res")
153 | }
154 | //https://developer.android.com/studio/test/gradle-managed-devices
155 | @Suppress("UnstableApiUsage")
156 | testOptions {
157 | managedDevices.devices {
158 | maybeCreate("pixel5").apply {
159 | device = "Pixel 5"
160 | apiLevel = 34
161 | systemImageSource = "aosp"
162 | }
163 | }
164 | }
165 | compileOptions {
166 | sourceCompatibility = JavaVersion.VERSION_1_8
167 | targetCompatibility = JavaVersion.VERSION_1_8
168 | }
169 | buildFeatures {
170 | compose = true
171 | }
172 | composeOptions {
173 | kotlinCompilerExtensionVersion = "1.5.11"
174 | }
175 | }
176 |
177 | compose.desktop {
178 | application {
179 | mainClass = "MainKt"
180 |
181 | nativeDistributions {
182 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
183 | packageName = "org.company.app.desktopApp"
184 | packageVersion = "1.0.0"
185 | }
186 | }
187 | }
188 |
189 | compose.experimental {
190 | web.application {}
191 | }
192 |
193 | buildConfig {
194 | // BuildConfig configuration here.
195 | // https://github.com/gmazzo/gradle-buildconfig-plugin#usage-in-kts
196 | }
197 |
198 | sqldelight {
199 | databases {
200 | create("MyDatabase") {
201 | // Database configuration here.
202 | // https://cashapp.github.io/sqldelight
203 | packageName.set("org.company.app.db")
204 | }
205 | }
206 | }
207 | task("testClasses"){}
--------------------------------------------------------------------------------
/composeApp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/org/company/app/App.android.kt:
--------------------------------------------------------------------------------
1 | package org.company.app
2 |
3 | import android.app.Application
4 | import android.content.Intent
5 | import android.net.Uri
6 | import android.os.Bundle
7 | import androidx.activity.ComponentActivity
8 | import androidx.activity.compose.setContent
9 | import androidx.activity.enableEdgeToEdge
10 | import org.company.app.di.appModule
11 | import org.koin.android.ext.koin.androidContext
12 | import org.koin.android.ext.koin.androidLogger
13 | import org.koin.core.context.startKoin
14 |
15 | class AndroidApp : Application() {
16 | companion object {
17 | lateinit var INSTANCE: AndroidApp
18 | }
19 |
20 | override fun onCreate() {
21 | super.onCreate()
22 | INSTANCE = this
23 | }
24 | }
25 |
26 | class AppActivity : ComponentActivity() {
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | enableEdgeToEdge()
30 | setContent { App() }
31 | }
32 | }
33 |
34 | internal actual fun openUrl(url: String?) {
35 | val uri = url?.let { Uri.parse(it) } ?: return
36 | val intent = Intent().apply {
37 | action = Intent.ACTION_VIEW
38 | data = uri
39 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
40 | }
41 | AndroidApp.INSTANCE.startActivity(intent)
42 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/org/company/app/theme/Theme.android.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.theme
2 |
3 | import android.app.Activity
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.LaunchedEffect
6 | import androidx.compose.ui.platform.LocalView
7 | import androidx.core.view.WindowInsetsControllerCompat
8 |
9 | @Composable
10 | internal actual fun SystemAppearance(isDark: Boolean) {
11 | val view = LocalView.current
12 | LaunchedEffect(isDark) {
13 | val window = (view.context as Activity).window
14 | WindowInsetsControllerCompat(window, window.decorView).apply {
15 | isAppearanceLightStatusBars = isDark
16 | isAppearanceLightNavigationBars = isDark
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/ic_cyclone.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/ic_dark_mode.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/ic_light_mode.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/ic_rotate_right.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/thumbs_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/composeApp/src/commonMain/composeResources/drawable/thumbs_down.png
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/thumbs_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/composeApp/src/commonMain/composeResources/drawable/thumbs_up.png
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/font/IndieFlower-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/composeApp/src/commonMain/composeResources/font/IndieFlower-Regular.ttf
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/font/PoetsenOne-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/composeApp/src/commonMain/composeResources/font/PoetsenOne-Regular.ttf
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Cyclone
3 | Open github
4 | Run
5 | Stop
6 | Theme
7 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/App.kt:
--------------------------------------------------------------------------------
1 | package org.company.app
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.RowScope
8 | import androidx.compose.foundation.layout.WindowInsets
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.ime
13 | import androidx.compose.foundation.layout.navigationBarsPadding
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.foundation.layout.windowInsetsPadding
16 | import androidx.compose.foundation.shape.RoundedCornerShape
17 | import androidx.compose.material.BottomNavigation
18 | import androidx.compose.material.BottomNavigationItem
19 | import androidx.compose.material.icons.Icons
20 | import androidx.compose.material.icons.filled.Analytics
21 | import androidx.compose.material.icons.filled.Home
22 | import androidx.compose.material.icons.filled.Newspaper
23 | import androidx.compose.material.icons.filled.Person
24 | import androidx.compose.material.icons.filled.Wallet
25 | import androidx.compose.material.icons.outlined.Analytics
26 | import androidx.compose.material.icons.outlined.Home
27 | import androidx.compose.material.icons.outlined.Newspaper
28 | import androidx.compose.material.icons.outlined.Person
29 | import androidx.compose.material.icons.outlined.Wallet
30 | import androidx.compose.material3.Icon
31 | import androidx.compose.material3.MaterialTheme
32 | import androidx.compose.material3.NavigationBar
33 | import androidx.compose.material3.NavigationBarItem
34 | import androidx.compose.material3.NavigationBarItemColors
35 | import androidx.compose.material3.Scaffold
36 | import androidx.compose.material3.Text
37 | import androidx.compose.material3.contentColorFor
38 | import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
39 | import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
40 | import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
41 | import androidx.compose.runtime.Composable
42 | import androidx.compose.runtime.getValue
43 | import androidx.compose.runtime.mutableStateOf
44 | import androidx.compose.runtime.saveable.rememberSaveable
45 | import androidx.compose.runtime.setValue
46 | import androidx.compose.ui.Modifier
47 | import androidx.compose.ui.draw.clip
48 | import androidx.compose.ui.graphics.Color
49 | import androidx.compose.ui.unit.dp
50 | import androidx.compose.ui.unit.sp
51 | import cafe.adriel.voyager.navigator.tab.CurrentTab
52 | import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
53 | import cafe.adriel.voyager.navigator.tab.Tab
54 | import cafe.adriel.voyager.navigator.tab.TabNavigator
55 | import coil3.ImageLoader
56 | import coil3.PlatformContext
57 | import coil3.annotation.ExperimentalCoilApi
58 | import coil3.compose.setSingletonImageLoaderFactory
59 | import coil3.request.CachePolicy
60 | import coil3.request.crossfade
61 | import coil3.util.DebugLogger
62 | import org.company.app.di.appModule
63 | import org.company.app.presentation.ui.navigation.rails.items.NavigationItem
64 | import org.company.app.presentation.ui.navigation.rails.navbar.NavigationSideBar
65 | import org.company.app.presentation.ui.navigation.tab.analytics.Analytics
66 | import org.company.app.presentation.ui.navigation.tab.home.Home
67 | import org.company.app.presentation.ui.navigation.tab.news.News
68 | import org.company.app.presentation.ui.navigation.tab.profile.Profile
69 | import org.company.app.theme.AppTheme
70 | import org.company.app.theme.LocalThemeIsDark
71 | import org.koin.compose.KoinApplication
72 | import org.koin.core.KoinApplication
73 |
74 | @OptIn(ExperimentalCoilApi::class)
75 | @Composable
76 | internal fun App() = AppTheme {
77 | setSingletonImageLoaderFactory { context ->
78 | getAsyncImageLoader(context)
79 | }
80 | KoinApplication(
81 | application = {
82 | modules(appModule)
83 | }
84 | ){
85 | AppContent()
86 | }
87 | }
88 |
89 | fun getAsyncImageLoader(context: PlatformContext) =
90 | ImageLoader.Builder(context)
91 | .crossfade(true)
92 | .logger(DebugLogger())
93 | .memoryCachePolicy(CachePolicy.ENABLED)
94 | .build()
95 |
96 | @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
97 | @Composable
98 | fun AppContent() {
99 | val items = listOf(
100 | NavigationItem(
101 | title = "Home",
102 | selectedIcon = Icons.Default.Home,
103 | unselectedIcon = Icons.Outlined.Home,
104 | hasNews = false,
105 | ),
106 | NavigationItem(
107 | title = "Analytics",
108 | selectedIcon = Icons.Filled.Analytics,
109 | unselectedIcon = Icons.Outlined.Analytics,
110 | hasNews = true,
111 | ),
112 | NavigationItem(
113 | title = "News",
114 | selectedIcon = Icons.Filled.Newspaper,
115 | unselectedIcon = Icons.Outlined.Newspaper,
116 | hasNews = false,
117 | ),
118 | NavigationItem(
119 | title = "Profile",
120 | selectedIcon = Icons.Filled.Person,
121 | unselectedIcon = Icons.Outlined.Person,
122 | hasNews = false,
123 | ),
124 | )
125 | val windowClass = calculateWindowSizeClass()
126 | val showNavigationRail = windowClass.widthSizeClass != WindowWidthSizeClass.Compact
127 | var selectedItemIndex by rememberSaveable {
128 | mutableStateOf(0)
129 | }
130 |
131 | TabNavigator(Home) { tabNavigator ->
132 | Scaffold(bottomBar = {
133 | if (!showNavigationRail) {
134 | NavigationBar(
135 | modifier = Modifier.fillMaxWidth().windowInsetsPadding(WindowInsets.ime),
136 | containerColor = MaterialTheme.colorScheme.background,
137 | contentColor = contentColorFor(Color.Red),
138 | tonalElevation = 8.dp
139 | ) {
140 | TabItem(Home)
141 | TabItem(Analytics)
142 | TabItem(News)
143 | TabItem(Profile)
144 | }
145 | }
146 | }) {
147 | Column(
148 | modifier = Modifier.fillMaxSize().padding(
149 | bottom = it.calculateBottomPadding(),
150 | start = if (showNavigationRail) 80.dp else 0.dp
151 | )
152 | ) {
153 | CurrentTab()
154 | }
155 | }
156 | }
157 | if (showNavigationRail) {
158 | NavigationSideBar(
159 | items = items,
160 | selectedItemIndex = selectedItemIndex,
161 | onNavigate = {
162 | selectedItemIndex = it
163 | }
164 | )
165 |
166 | Box(
167 | modifier = Modifier.fillMaxSize()
168 | .padding(start = 80.dp)
169 | ) {
170 | when (selectedItemIndex) {
171 | 0 -> {
172 |
173 | }
174 |
175 | 1 -> {
176 | Box(
177 | modifier = Modifier
178 | .fillMaxSize()
179 | .background(MaterialTheme.colorScheme.background)
180 | ) {
181 | TabNavigator(Analytics)
182 | }
183 | }
184 |
185 | 2 -> {
186 | Box(
187 | modifier = Modifier
188 | .fillMaxSize()
189 | .background(MaterialTheme.colorScheme.background)
190 | ) {
191 | TabNavigator(News)
192 | }
193 | }
194 |
195 | 3 -> {
196 | Box(
197 | modifier = Modifier
198 | .fillMaxSize()
199 | .background(MaterialTheme.colorScheme.background)
200 | ) {
201 | TabNavigator(Profile)
202 | }
203 | }
204 | }
205 | }
206 |
207 | }
208 | }
209 |
210 | @Composable
211 | fun RowScope.TabItem(tab: Tab) {
212 | val isDark by LocalThemeIsDark.current
213 | val tabNavigator = LocalTabNavigator.current
214 | NavigationBarItem(
215 | modifier = Modifier
216 | .height(58.dp).clip(RoundedCornerShape(16.dp)),
217 | selected = tabNavigator.current == tab,
218 | onClick = {
219 | tabNavigator.current = tab
220 | },
221 | icon = {
222 | tab.options.icon?.let { painter ->
223 | Icon(
224 | painter,
225 | contentDescription = tab.options.title,
226 | tint = if (tabNavigator.current == tab) Color.Red else if (isDark) Color.White else Color.Black
227 | )
228 | }
229 | },
230 | label = {
231 | tab.options.title.let { title ->
232 | Text(
233 | title,
234 | fontSize = 12.sp,
235 | color = if (tabNavigator.current == tab) Color.Red else if (isDark) Color.White else Color.Black
236 | )
237 | }
238 | },
239 | enabled = true,
240 | alwaysShowLabel = true,
241 | interactionSource = MutableInteractionSource(),
242 | colors = NavigationBarItemColors(
243 | selectedTextColor = Color.Red,
244 | unselectedIconColor = Color.Black,
245 | selectedIconColor = Color.Red,
246 | unselectedTextColor = Color.Black,
247 | selectedIndicatorColor = Color.LightGray,
248 | disabledIconColor = Color.Transparent,
249 | disabledTextColor = Color.Transparent
250 | )
251 | )
252 | }
253 |
254 | internal expect fun openUrl(url: String?)
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/data/remote/CryptoClient.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.data.remote
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.call.body
5 | import io.ktor.client.plugins.HttpTimeout
6 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
7 | import io.ktor.client.plugins.defaultRequest
8 | import io.ktor.client.plugins.logging.LogLevel
9 | import io.ktor.client.plugins.logging.Logger
10 | import io.ktor.client.plugins.logging.Logging
11 | import io.ktor.client.request.get
12 | import io.ktor.client.request.headers
13 | import io.ktor.serialization.kotlinx.json.json
14 | import kotlinx.serialization.json.Json
15 | import org.company.app.domain.model.categories.NewsCategoriesItem
16 | import org.company.app.domain.model.crypto.LatestListing
17 | import org.company.app.domain.model.news.NewsList
18 | import org.company.app.utils.Constant.API_KEY
19 | import org.company.app.utils.Constant.BASE_URL
20 | import org.company.app.utils.Constant.CRYPTO_URL
21 | import org.company.app.utils.Constant.TIME_OUT
22 |
23 | class CryptoClient(private val client: HttpClient) {
24 | suspend fun getLatestListing(): LatestListing {
25 | return client.get(BASE_URL + "cryptocurrency/listings/latest").body()
26 | }
27 | suspend fun getAllNews(): NewsList{
28 | return client.get(CRYPTO_URL + "v2/news/?lang=EN").body()
29 | }
30 | suspend fun getNewsCategories(): List{
31 | return client.get( CRYPTO_URL+"news/categories").body()
32 |
33 | }
34 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/data/repository/CryptoApi.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.data.repository
2 |
3 | import org.company.app.domain.model.categories.NewsCategoriesItem
4 | import org.company.app.domain.model.crypto.LatestListing
5 | import org.company.app.domain.model.news.NewsList
6 |
7 | interface CryptoApi {
8 | suspend fun getLatestListing(): LatestListing
9 | suspend fun getAllNews(): NewsList
10 | suspend fun getNewsCategories(): List
11 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/di/appModule.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.di
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.plugins.HttpTimeout
5 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
6 | import io.ktor.client.plugins.defaultRequest
7 | import io.ktor.client.plugins.logging.LogLevel
8 | import io.ktor.client.plugins.logging.Logger
9 | import io.ktor.client.plugins.logging.Logging
10 | import io.ktor.client.request.headers
11 | import io.ktor.http.HttpHeaders
12 | import io.ktor.serialization.kotlinx.json.json
13 | import kotlinx.serialization.json.Json
14 | import org.company.app.data.remote.CryptoClient
15 | import org.company.app.domain.repository.Repository
16 | import org.company.app.presentation.viewmodel.MainViewModel
17 | import org.company.app.utils.Constant
18 | import org.koin.core.module.dsl.singleOf
19 | import org.koin.dsl.module
20 |
21 | val appModule = module {
22 | single {
23 | HttpClient {
24 | install(ContentNegotiation) {
25 | json(
26 | json = Json {
27 | isLenient = true
28 | ignoreUnknownKeys = true
29 | }
30 | )
31 | }
32 | install(Logging) {
33 | level = LogLevel.ALL
34 | logger = object : Logger {
35 | override fun log(message: String) {
36 | println(message)
37 | }
38 | }
39 | filter { filter-> filter.url.host.contains("coinmarketcap.com") }
40 | sanitizeHeader { header-> header == HttpHeaders.Authorization }
41 | }
42 | install(HttpTimeout) {
43 | requestTimeoutMillis = Constant.TIME_OUT
44 | connectTimeoutMillis = Constant.TIME_OUT
45 | socketTimeoutMillis = Constant.TIME_OUT
46 | }
47 | defaultRequest {
48 | headers {
49 | append("X-CMC_PRO_API_KEY", Constant.API_KEY)
50 | }
51 | }
52 | }
53 | }
54 | single { CryptoClient(get()) }
55 | single {
56 | Repository(get())
57 | }
58 | singleOf(::MainViewModel)
59 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/categories/NewsCategoriesItem.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.categories
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class NewsCategoriesItem(
9 | @SerialName("categoryName")
10 | val categoryName: String = "",
11 | @SerialName("excludedPhrases")
12 | val excludedPhrases: List? = null,
13 | @SerialName("includedPhrases")
14 | val includedPhrases: List? = null,
15 | @SerialName("wordsAssociatedWithCategory")
16 | val wordsAssociatedWithCategory: List? = null
17 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/crypto/Data.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.crypto
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class Data(
9 | @SerialName("circulating_supply")
10 | val circulatingSupply: Double = 0.0,
11 | @SerialName("cmc_rank")
12 | val cmcRank: Int = 0,
13 | @SerialName("date_added")
14 | val dateAdded: String = "",
15 | @SerialName("id")
16 | val id: Int = 0,
17 | @SerialName("infinite_supply")
18 | val infiniteSupply: Boolean = false,
19 | @SerialName("last_updated")
20 | val lastUpdated: String = "",
21 | @SerialName("max_supply")
22 | val maxSupply: Double? = null,
23 | @SerialName("name")
24 | val name: String = "",
25 | @SerialName("num_market_pairs")
26 | val numMarketPairs: Int = 0,
27 | @SerialName("platform")
28 | val platform: Platform? = null,
29 | @SerialName("quote")
30 | val quote: Quote = Quote(),
31 | @SerialName("self_reported_circulating_supply")
32 | val selfReportedCirculatingSupply: Double? = null,
33 | @SerialName("self_reported_market_cap")
34 | val selfReportedMarketCap: Double? = null,
35 | @SerialName("slug")
36 | val slug: String = "",
37 | @SerialName("symbol")
38 | val symbol: String = "",
39 | @SerialName("tags")
40 | val tags: List = listOf(),
41 | @SerialName("total_supply")
42 | val totalSupply: Double = 0.0,
43 | @SerialName("tvl_ratio")
44 | val tvlRatio: Double? = null
45 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/crypto/LatestListing.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.crypto
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class LatestListing(
9 | @SerialName("data")
10 | val `data`: List = listOf(),
11 | @SerialName("status")
12 | val status: Status = Status()
13 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/crypto/Platform.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.crypto
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class Platform(
9 | @SerialName("id")
10 | val id: Int = 0,
11 | @SerialName("name")
12 | val name: String = "",
13 | @SerialName("slug")
14 | val slug: String = "",
15 | @SerialName("symbol")
16 | val symbol: String = "",
17 | @SerialName("token_address")
18 | val tokenAddress: String = ""
19 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/crypto/Quote.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.crypto
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class Quote(
9 | @SerialName("USD")
10 | val uSD: USD = USD()
11 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/crypto/Status.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.crypto
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class Status(
9 | @SerialName("credit_count")
10 | val creditCount: Int = 0,
11 | @SerialName("elapsed")
12 | val elapsed: Int = 0,
13 | @SerialName("error_code")
14 | val errorCode: Int = 0,
15 | @SerialName("error_message")
16 | val errorMessage: String? = null,
17 | @SerialName("notice")
18 | val notice: String? = null,
19 | @SerialName("timestamp")
20 | val timestamp: String = "",
21 | @SerialName("total_count")
22 | val totalCount: Int = 0
23 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/crypto/USD.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.crypto
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class USD(
9 | @SerialName("fully_diluted_market_cap")
10 | val fullyDilutedMarketCap: Double = 0.0,
11 | @SerialName("last_updated")
12 | val lastUpdated: String = "",
13 | @SerialName("market_cap")
14 | val marketCap: Double = 0.0,
15 | @SerialName("market_cap_dominance")
16 | val marketCapDominance: Double = 0.0,
17 | @SerialName("percent_change_1h")
18 | val percentChange1h: Double = 0.0,
19 | @SerialName("percent_change_24h")
20 | val percentChange24h: Double = 0.0,
21 | @SerialName("percent_change_30d")
22 | val percentChange30d: Double = 0.0,
23 | @SerialName("percent_change_60d")
24 | val percentChange60d: Double = 0.0,
25 | @SerialName("percent_change_7d")
26 | val percentChange7d: Double = 0.0,
27 | @SerialName("percent_change_90d")
28 | val percentChange90d: Double = 0.0,
29 | @SerialName("price")
30 | val price: Double = 0.0,
31 | @SerialName("tvl")
32 | val tvl: Double? = null,
33 | @SerialName("volume_24h")
34 | val volume24h: Double = 0.0,
35 | @SerialName("volume_change_24h")
36 | val volumeChange24h: Double = 0.0
37 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/news/Data.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.news
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class Data(
9 | @SerialName("body")
10 | val body: String = "",
11 | @SerialName("categories")
12 | val categories: String = "",
13 | @SerialName("downvotes")
14 | val downvotes: String = "",
15 | @SerialName("guid")
16 | val guid: String = "",
17 | @SerialName("id")
18 | val id: String = "",
19 | @SerialName("imageurl")
20 | val imageurl: String = "",
21 | @SerialName("lang")
22 | val lang: String = "",
23 | @SerialName("published_on")
24 | val publishedOn: Int = 0,
25 | @SerialName("source")
26 | val source: String = "",
27 | @SerialName("source_info")
28 | val sourceInfo: SourceInfo = SourceInfo(),
29 | @SerialName("tags")
30 | val tags: String = "",
31 | @SerialName("title")
32 | val title: String = "",
33 | @SerialName("upvotes")
34 | val upvotes: String = "",
35 | @SerialName("url")
36 | val url: String = ""
37 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/news/NewsList.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.news
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class NewsList(
9 | @SerialName("Data")
10 | val `data`: List = listOf(),
11 | @SerialName("HasWarning")
12 | val hasWarning: Boolean = false,
13 | @SerialName("Message")
14 | val message: String = "",
15 | @SerialName("Promoted")
16 | val promoted: List = listOf(),
17 | @SerialName("RateLimit")
18 | val rateLimit: RateLimit = RateLimit(),
19 | @SerialName("Type")
20 | val type: Int = 0
21 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/news/RateLimit.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.news
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | class RateLimit
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/model/news/SourceInfo.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.model.news
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class SourceInfo(
9 | @SerialName("img")
10 | val img: String = "",
11 | @SerialName("lang")
12 | val lang: String = "",
13 | @SerialName("name")
14 | val name: String = ""
15 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/repository/Repository.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.repository
2 |
3 | import org.company.app.data.remote.CryptoClient
4 | import org.company.app.data.repository.CryptoApi
5 | import org.company.app.domain.model.categories.NewsCategoriesItem
6 | import org.company.app.domain.model.crypto.LatestListing
7 | import org.company.app.domain.model.news.NewsList
8 |
9 | class Repository(
10 | private val cryptoClient: CryptoClient
11 | ): CryptoApi {
12 | override suspend fun getLatestListing(): LatestListing {
13 | return cryptoClient.getLatestListing()
14 | }
15 |
16 | override suspend fun getAllNews(): NewsList {
17 | return cryptoClient.getAllNews()
18 | }
19 |
20 | override suspend fun getNewsCategories(): List {
21 | return cryptoClient.getNewsCategories()
22 | }
23 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/domain/usecase/ResultState.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.domain.usecase
2 |
3 | sealed class ResultState {
4 | object LOADING: ResultState()
5 | data class SUCCESS(val response: T): ResultState()
6 | data class ERROR(val message: String): ResultState()
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/ChartImage.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.animation.core.LinearOutSlowInEasing
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.foundation.layout.BoxScope
6 | import androidx.compose.material3.LinearProgressIndicator
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.ColorFilter
11 | import androidx.compose.ui.graphics.painter.Painter
12 | import androidx.compose.ui.layout.ContentScale
13 | import io.kamel.core.Resource
14 | import io.kamel.image.KamelImage
15 | import io.kamel.image.asyncPainterResource
16 |
17 | @Composable
18 | fun ChartImage(
19 | id: Int,
20 | modifier: Modifier,
21 | tintColor: Color,
22 | ) {
23 | val image: Resource =
24 | asyncPainterResource("https://s3.coinmarketcap.com/generated/sparklines/web/7d/2781/$id.svg")
25 | KamelImage(
26 | { image }, contentDescription = null,
27 | modifier = modifier,
28 | contentScale = ContentScale.FillWidth,
29 | colorFilter = ColorFilter.tint(color = tintColor),
30 | onLoading = {
31 | LinearProgressIndicator(
32 | progress = { it },
33 | )
34 | }, animationSpec = tween(
35 | durationMillis = 300,
36 | delayMillis = 300,
37 | easing = LinearOutSlowInEasing
38 | )
39 | )
40 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/CryptoChart.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.material3.Checkbox
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.text.TextStyle
21 | import androidx.compose.ui.text.font.FontWeight
22 | import androidx.compose.ui.unit.dp
23 | import androidx.compose.ui.unit.sp
24 | import com.aay.compose.baseComponents.model.GridOrientation
25 | import com.aay.compose.baseComponents.model.LegendPosition
26 | import com.aay.compose.donutChart.PieChart
27 | import com.aay.compose.donutChart.model.PieChartData
28 | import com.aay.compose.lineChart.LineChart
29 | import com.aay.compose.lineChart.model.LineParameters
30 | import com.aay.compose.lineChart.model.LineType
31 | import org.company.app.domain.model.crypto.Data
32 | import kotlin.math.abs
33 | import kotlin.random.Random
34 |
35 | @Composable
36 | fun CryptoChart(
37 | dataList: Data,
38 | selectedPeriod: String,
39 | ) {
40 | val relevantData = when (selectedPeriod) {
41 | "1H" -> listOf(
42 | dataList.quote.uSD.percentChange1h,
43 | dataList.quote.uSD.percentChange24h,
44 | dataList.quote.uSD.percentChange7d,
45 | dataList.quote.uSD.percentChange30d,
46 | dataList.quote.uSD.percentChange60d,
47 | dataList.quote.uSD.percentChange90d,
48 | )
49 |
50 | "1D" -> listOf(
51 | Random.nextDouble(0.0, 7.0),
52 | dataList.quote.uSD.percentChange24h,
53 | dataList.quote.uSD.percentChange7d,
54 | dataList.quote.uSD.percentChange30d,
55 | dataList.quote.uSD.percentChange60d,
56 | dataList.quote.uSD.percentChange90d,
57 | )
58 |
59 | "1W" -> listOf(
60 | Random.nextDouble(0.0, 7.0),
61 | Random.nextDouble(0.0, 7.0),
62 | dataList.quote.uSD.percentChange7d,
63 | dataList.quote.uSD.percentChange30d,
64 | dataList.quote.uSD.percentChange60d,
65 | dataList.quote.uSD.percentChange90d,
66 | )
67 |
68 | "1M" -> listOf(
69 | Random.nextDouble(0.0, 7.0),
70 | Random.nextDouble(0.0, 7.0),
71 | Random.nextDouble(0.0, 7.0),
72 | dataList.quote.uSD.percentChange30d,
73 | dataList.quote.uSD.percentChange60d,
74 | dataList.quote.uSD.percentChange90d,
75 | )
76 |
77 | "3M" -> listOf(
78 | Random.nextDouble(0.0, 7.0),
79 | Random.nextDouble(0.0, 7.0),
80 | Random.nextDouble(0.0, 7.0),
81 | Random.nextDouble(0.0, 7.0),
82 | dataList.quote.uSD.percentChange60d,
83 | dataList.quote.uSD.percentChange90d,
84 | )
85 |
86 | "6M" -> listOf(
87 | Random.nextDouble(0.0, 7.0),
88 | Random.nextDouble(0.0, 7.0),
89 | Random.nextDouble(0.0, 7.0),
90 | Random.nextDouble(0.0, 7.0),
91 | Random.nextDouble(0.0, 7.0),
92 | dataList.quote.uSD.percentChange90d,
93 | )
94 |
95 | "1Y" -> listOf(
96 | Random.nextDouble(0.0, 7.0),
97 | Random.nextDouble(0.0, 7.0),
98 | Random.nextDouble(0.0, 7.0),
99 | Random.nextDouble(0.0, 7.0),
100 | Random.nextDouble(0.0, 7.0),
101 | dataList.quote.uSD.percentChange90d,
102 | )
103 |
104 | else -> emptyList()
105 | }
106 |
107 | var isPieCharEnabled by remember { mutableStateOf(false) }
108 | val dataList1 = mutableListOf()
109 | dataList1.add(dataList.quote.uSD.percentChange1h)
110 | dataList1.add(dataList.quote.uSD.percentChange24h)
111 | dataList1.add(dataList.quote.uSD.percentChange7d)
112 | dataList1.add(dataList.quote.uSD.percentChange30d)
113 | dataList1.add(dataList.quote.uSD.percentChange60d)
114 | dataList1.add(dataList.quote.uSD.percentChange90d)
115 | println("DataList: $dataList1")
116 | val positiveDataList = relevantData.map { abs(it) }
117 | println("DataList: $positiveDataList")
118 |
119 | val testLineParameters: List = listOf(
120 | LineParameters(
121 | label = "Price",
122 | data = positiveDataList,
123 | lineColor = Color.Red,
124 | lineType = LineType.CURVED_LINE,
125 | lineShadow = true,
126 | )
127 | )
128 |
129 | Column(
130 | modifier = Modifier.fillMaxWidth(),
131 | horizontalAlignment = Alignment.CenterHorizontally,
132 | verticalArrangement = Arrangement.Center
133 | ) {
134 | if (!isPieCharEnabled) {
135 | LineChart(
136 | modifier = Modifier.fillMaxWidth()
137 | .height(270.dp),
138 | linesParameters = testLineParameters,
139 | isGrid = false,
140 | gridColor = Color.Blue,
141 | xAxisData = listOf(
142 | "2016",
143 | "2018",
144 | "2020",
145 | "2022",
146 | "2023",
147 | "2024"
148 | ),
149 | animateChart = true,
150 | showGridWithSpacer = true,
151 | legendPosition = LegendPosition.TOP,
152 | yAxisStyle = TextStyle(
153 | fontSize = 14.sp,
154 | color = Color.Gray,
155 | ),
156 | xAxisStyle = TextStyle(
157 | fontSize = 14.sp,
158 | color = Color.Gray,
159 | fontWeight = FontWeight.W400
160 | ),
161 | yAxisRange = 6,
162 | oneLineChart = false,
163 | gridOrientation = GridOrientation.VERTICAL
164 | )
165 | } else {
166 | val testPieChartData: List = listOf(
167 | PieChartData(
168 | partName = "1H",
169 | data = 40.32,
170 | color = Color(0xFF22A699),
171 | ),
172 | PieChartData(
173 | partName = "24H",
174 | data = 65.02,
175 | color = Color(0xFFF2BE22),
176 | ),
177 | PieChartData(
178 | partName = "7D",
179 | data = 42.32,
180 | color = Color(0xFFF29727),
181 | ),
182 | PieChartData(
183 | partName = "1M",
184 | data = 15.32,
185 | color = Color(0xFFF24C3D),
186 | ),
187 | PieChartData(
188 | partName = "2M",
189 | data = 90.2,
190 | color = Color(0xFFF24C3D),
191 | ),
192 | PieChartData(
193 | partName = "3M",
194 | data = 55.4,
195 | color = Color(0xFFF24C3D),
196 | ),
197 | )
198 |
199 | PieChart(
200 | modifier = Modifier.fillMaxWidth()
201 | .height(270.dp),
202 | pieChartData = testPieChartData,
203 | ratioLineColor = Color.LightGray,
204 | textRatioStyle = TextStyle(color = Color.Gray),
205 | )
206 | }
207 |
208 | Spacer(modifier = Modifier.height(16.dp))
209 | Row(
210 | modifier = Modifier.fillMaxWidth(),
211 | horizontalArrangement = Arrangement.Start,
212 | verticalAlignment = Alignment.CenterVertically
213 | ) {
214 | Checkbox(
215 | checked = isPieCharEnabled,
216 | onCheckedChange = {
217 | isPieCharEnabled = it
218 | }
219 | )
220 | Text(
221 | text = dataList.symbol + " Pie Chart",
222 | fontSize = MaterialTheme.typography.titleMedium.fontSize,
223 | fontWeight = FontWeight.Bold
224 | )
225 | }
226 | }
227 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/CryptoList.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.layout.size
12 | import androidx.compose.foundation.layout.width
13 | import androidx.compose.material.icons.Icons
14 | import androidx.compose.material.icons.filled.KeyboardArrowDown
15 | import androidx.compose.material3.HorizontalDivider
16 | import androidx.compose.material3.Icon
17 | import androidx.compose.material3.MaterialTheme
18 | import androidx.compose.material3.Text
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.getValue
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.graphics.Color
24 | import androidx.compose.ui.text.font.FontWeight
25 | import androidx.compose.ui.unit.dp
26 | import androidx.compose.ui.unit.sp
27 | import cafe.adriel.voyager.navigator.LocalNavigator
28 | import org.company.app.domain.model.crypto.Data
29 | import org.company.app.presentation.ui.screens.detail.DetailScreen
30 | import org.company.app.theme.LocalThemeIsDark
31 | import kotlin.math.roundToInt
32 |
33 | @Composable
34 | fun CryptoList(
35 | dataList: List,
36 | coinsText: String,
37 | viewText: String,
38 | largeCapColor: Color = Color.Black,
39 | isCapIconEnabled: Boolean = false,
40 | ) {
41 | val isDark by LocalThemeIsDark.current
42 | val viewALlColor =
43 | if (viewText.contains("View All")) if (isDark) Color.White else Color.Black else largeCapColor
44 | val textColor = if (isDark) Color.White else Color.Black
45 | Column(
46 | modifier = Modifier.fillMaxWidth(),
47 | horizontalAlignment = Alignment.CenterHorizontally,
48 | verticalArrangement = Arrangement.Center
49 | ) {
50 | Row(
51 | modifier = Modifier.fillMaxWidth()
52 | .padding(start = 3.dp, end = 3.dp),
53 | verticalAlignment = Alignment.Top,
54 | horizontalArrangement = Arrangement.SpaceBetween
55 | ) {
56 | Text(
57 | text = coinsText,
58 | fontSize = 17.sp,
59 | fontWeight = FontWeight.Bold,
60 | color = textColor
61 | )
62 | Row(
63 | verticalAlignment = Alignment.CenterVertically,
64 | horizontalArrangement = Arrangement.Center
65 | ) {
66 | Text(
67 | text = viewText,
68 | fontSize = 14.sp,
69 | fontWeight = FontWeight.Bold,
70 | color = viewALlColor
71 | )
72 | AnimatedVisibility(isCapIconEnabled) {
73 | Icon(
74 | modifier = Modifier.size(25.dp),
75 | imageVector = Icons.Default.KeyboardArrowDown,
76 | contentDescription = null,
77 | tint = largeCapColor
78 | )
79 | }
80 | }
81 | }
82 | Column(
83 | modifier = Modifier
84 | .fillMaxWidth()
85 | ) {
86 | dataList.forEach { data ->
87 | CryptoItem(data = data)
88 | HorizontalDivider()
89 | }
90 | }
91 | }
92 | }
93 |
94 | @Composable
95 | fun CryptoItem(data: Data) {
96 | val navigator = LocalNavigator.current
97 | val isDark by LocalThemeIsDark.current
98 | val textColor = if (isDark) Color.White else Color.Black
99 | val percentChange24h = data.quote.uSD.percentChange24h
100 | val textColor24h = if (percentChange24h > 0) Color.Green else Color.Red
101 | Column(
102 | modifier = Modifier
103 | .fillMaxWidth()
104 | .padding(8.dp)
105 | .clickable {
106 | navigator?.push(DetailScreen(data))
107 | },
108 | horizontalAlignment = Alignment.Start,
109 | verticalArrangement = Arrangement.Center
110 | ) {
111 | Row(
112 | modifier = Modifier.fillMaxWidth(),
113 | horizontalArrangement = Arrangement.Start,
114 | verticalAlignment = Alignment.CenterVertically
115 | ) {
116 | Text(
117 | text = data.cmcRank.toString(),
118 | fontSize = MaterialTheme.typography.labelMedium.fontSize,
119 | color = textColor
120 | )
121 | Spacer(modifier = Modifier.width(50.dp))
122 | Row(
123 | verticalAlignment = Alignment.CenterVertically
124 | ) {
125 | CurrencyImage(
126 | id = data.id,
127 | modifier = Modifier.size(35.dp)
128 | )
129 | Spacer(modifier = Modifier.width(6.dp))
130 | Column(
131 | horizontalAlignment = Alignment.Start,
132 | verticalArrangement = Arrangement.SpaceAround
133 | ) {
134 | Text(
135 | text = data.name,
136 | fontSize = 14.sp,
137 | fontWeight = FontWeight.Bold,
138 | color = textColor
139 | )
140 | Text(
141 | text = data.symbol,
142 | fontSize = 11.sp,
143 | fontWeight = FontWeight.Bold,
144 | color = Color.Gray
145 | )
146 | }
147 | }
148 | Spacer(modifier = Modifier.weight(1f))
149 | ChartImage(
150 | id = data.id,
151 | modifier = Modifier.fillMaxWidth(0.40f),
152 | tintColor = textColor24h
153 | )
154 | Spacer(modifier = Modifier.weight(1f))
155 | Column(
156 | horizontalAlignment = Alignment.End,
157 | verticalArrangement = Arrangement.Center
158 | ) {
159 | Text(
160 | text = "$" + "${((data.quote.uSD.price * 100).roundToInt()) / 100.0}",
161 | fontSize = MaterialTheme.typography.titleMedium.fontSize,
162 | fontWeight = FontWeight.Bold,
163 | color = textColor
164 | )
165 | Spacer(modifier = Modifier.weight(1f))
166 | Text(
167 | text = "${percentChange24h.roundToInt()}%",
168 | color = textColor24h
169 | )
170 | }
171 | }
172 | }
173 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/CurrencyImage.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.material3.CircularProgressIndicator
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.painter.Painter
10 | import androidx.compose.ui.layout.ContentScale
11 | import coil3.compose.AsyncImage
12 | import io.kamel.core.Resource
13 | import io.kamel.image.KamelImage
14 | import io.kamel.image.asyncPainterResource
15 |
16 | @Composable
17 | fun CurrencyImage(
18 | id: Int,
19 | modifier: Modifier,
20 | ) {
21 | val image: Resource = asyncPainterResource("https://s2.coinmarketcap.com/static/img/coins/64x64/$id.png")
22 | KamelImage(
23 | { image }, contentDescription = null,
24 | modifier = modifier,
25 | contentScale = ContentScale.FillBounds,
26 | onLoading = {
27 | Box(
28 | modifier = Modifier.fillMaxWidth(),
29 | contentAlignment = Alignment.Center
30 | ) {
31 | CircularProgressIndicator(progress = {it})
32 | }
33 | }
34 | )
35 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/ErrorBox.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.text.selection.SelectionContainer
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.unit.sp
12 |
13 | @Composable
14 | fun ErrorBox(error: String) {
15 | Box(
16 | modifier = Modifier.fillMaxSize(),
17 | contentAlignment = Alignment.Center
18 | ) {
19 | SelectionContainer {
20 | Text(
21 | text = error,
22 | color = Color.Red,
23 | fontSize = 24.sp
24 | )
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/LoadingBox.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.material3.CircularProgressIndicator
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 |
12 | @Composable
13 | fun LoadingBox() {
14 | Box(
15 | modifier = Modifier.fillMaxSize(),
16 | contentAlignment = Alignment.Center
17 | ) {
18 | CircularProgressIndicator(
19 | color = Color.Blue
20 | )
21 | }
22 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/MarketRow.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.text.font.FontWeight
10 | import androidx.compose.ui.unit.sp
11 |
12 | @Composable
13 | fun MarketDataRow(
14 | title: String,
15 | value: String,
16 | isDark: Boolean,
17 | ) {
18 | Column(
19 | horizontalAlignment = Alignment.CenterHorizontally,
20 | verticalArrangement = Arrangement.Center
21 | ) {
22 | Text(
23 | text = title,
24 | fontSize = 14.sp,
25 | color = Color.Gray
26 | )
27 | Text(
28 | text = value,
29 | fontSize = 22.sp,
30 | color = if (isDark) Color.White else Color.Black,
31 | fontWeight = FontWeight.Bold
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/NewsDetail.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.height
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.foundation.layout.width
14 | import androidx.compose.foundation.rememberScrollState
15 | import androidx.compose.foundation.shape.CircleShape
16 | import androidx.compose.foundation.shape.RoundedCornerShape
17 | import androidx.compose.foundation.verticalScroll
18 | import androidx.compose.material.icons.Icons
19 | import androidx.compose.material.icons.filled.ArrowBackIosNew
20 | import androidx.compose.material3.Button
21 | import androidx.compose.material3.Divider
22 | import androidx.compose.material3.ExperimentalMaterial3Api
23 | import androidx.compose.material3.HorizontalDivider
24 | import androidx.compose.material3.Icon
25 | import androidx.compose.material3.MaterialTheme
26 | import androidx.compose.material3.Scaffold
27 | import androidx.compose.material3.Text
28 | import androidx.compose.material3.TopAppBar
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.ui.Alignment
31 | import androidx.compose.ui.Modifier
32 | import androidx.compose.ui.draw.clip
33 | import androidx.compose.ui.graphics.painter.Painter
34 | import androidx.compose.ui.layout.ContentScale
35 | import androidx.compose.ui.unit.dp
36 | import cafe.adriel.voyager.core.screen.Screen
37 | import cafe.adriel.voyager.navigator.LocalNavigator
38 | import coil3.compose.AsyncImage
39 | import io.kamel.core.Resource
40 | import io.kamel.image.KamelImage
41 | import io.kamel.image.asyncPainterResource
42 | import org.company.app.domain.model.news.Data
43 | import org.company.app.utils.formatTimestamp
44 |
45 | class NewsDetailScreen(private val news: Data) : Screen {
46 | @OptIn(ExperimentalMaterial3Api::class)
47 | @Composable
48 | override fun Content() {
49 | val navigator = LocalNavigator.current
50 | Scaffold(
51 | modifier = Modifier.fillMaxWidth(),
52 | topBar = {
53 | TopAppBar(
54 | title = {Text(text = news.title.take(15))},
55 | navigationIcon = {
56 | Icon(
57 | imageVector = Icons.Default.ArrowBackIosNew,
58 | contentDescription = "Arrow Back",
59 | modifier = Modifier.clickable {
60 | navigator?.pop()
61 | }
62 | )
63 | }
64 | )
65 | }
66 | ){
67 | Column(
68 | modifier = Modifier.fillMaxWidth()
69 | .padding(top = it.calculateTopPadding())
70 | .verticalScroll(rememberScrollState()),
71 | horizontalAlignment = Alignment.CenterHorizontally,
72 | verticalArrangement = Arrangement.spacedBy(12.dp)
73 | ) {
74 | NewsDetailContent(news)
75 | }
76 | }
77 | }
78 | }
79 |
80 | @Composable
81 | fun NewsDetailContent(news: Data) {
82 | Column(
83 | modifier = Modifier
84 | .fillMaxSize()
85 | .padding(16.dp)
86 | ) {
87 | val image: Resource = asyncPainterResource(news.imageurl)
88 | KamelImage(
89 | { image }, contentDescription = null,
90 | modifier = Modifier
91 | .height(250.dp)
92 | .fillMaxWidth()
93 | .clip(RoundedCornerShape(16.dp)),
94 | contentScale = ContentScale.Crop
95 | )
96 | Spacer(modifier = Modifier.height(16.dp))
97 | Text(
98 | text = news.title,
99 | style = MaterialTheme.typography.headlineMedium,
100 | color = MaterialTheme.colorScheme.onBackground
101 | )
102 | Spacer(modifier = Modifier.height(8.dp))
103 | Row(verticalAlignment = Alignment.CenterVertically) {
104 | AsyncImage(
105 | model = news.sourceInfo.img,
106 | contentDescription = null,
107 | modifier = Modifier
108 | .size(24.dp)
109 | .clip(CircleShape)
110 | )
111 | Spacer(modifier = Modifier.width(8.dp))
112 | Text(
113 | text = news.sourceInfo.name,
114 | style = MaterialTheme.typography.labelMedium,
115 | color = MaterialTheme.colorScheme.primary
116 | )
117 | }
118 | Spacer(modifier = Modifier.height(8.dp))
119 | Text(
120 | text = "Published: ${formatTimestamp(news.publishedOn.toLong())}",
121 | style = MaterialTheme.typography.bodySmall,
122 | color = MaterialTheme.colorScheme.onSurfaceVariant
123 | )
124 | Spacer(modifier = Modifier.height(16.dp))
125 | HorizontalDivider()
126 | Spacer(modifier = Modifier.height(16.dp))
127 | Text(
128 | text = news.body,
129 | style = MaterialTheme.typography.bodyLarge,
130 | color = MaterialTheme.colorScheme.onBackground
131 | )
132 | Spacer(modifier = Modifier.height(16.dp))
133 | HorizontalDivider()
134 | Spacer(modifier = Modifier.height(16.dp))
135 | Text(
136 | text = "Tags: ${news.tags}",
137 | style = MaterialTheme.typography.bodyMedium,
138 | color = MaterialTheme.colorScheme.primary
139 | )
140 | Spacer(modifier = Modifier.height(8.dp))
141 | Text(
142 | text = "Categories: ${news.categories}",
143 | style = MaterialTheme.typography.bodyMedium,
144 | color = MaterialTheme.colorScheme.primary
145 | )
146 | Spacer(modifier = Modifier.height(16.dp))
147 | HorizontalDivider()
148 | Spacer(modifier = Modifier.height(16.dp))
149 | Row(
150 | verticalAlignment = Alignment.CenterVertically,
151 | horizontalArrangement = Arrangement.SpaceBetween,
152 | modifier = Modifier.fillMaxWidth()
153 | ) {
154 | Text(
155 | text = "Upvotes: ${news.upvotes}",
156 | style = MaterialTheme.typography.bodySmall,
157 | color = MaterialTheme.colorScheme.onSurfaceVariant
158 | )
159 | Text(
160 | text = "Downvotes: ${news.downvotes}",
161 | style = MaterialTheme.typography.bodySmall,
162 | color = MaterialTheme.colorScheme.onSurfaceVariant
163 | )
164 | }
165 | Spacer(modifier = Modifier.height(16.dp))
166 | Button(
167 | onClick = { /* Handle URL click */ },
168 | modifier = Modifier.fillMaxWidth()
169 | ) {
170 | Text(text = "Read Full Article")
171 | }
172 | }
173 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/PromotionalCard.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import org.company.app.domain.model.news.Data
4 | import androidx.compose.animation.core.FastOutSlowInEasing
5 | import androidx.compose.animation.core.LinearOutSlowInEasing
6 | import androidx.compose.animation.core.animateDpAsState
7 | import androidx.compose.animation.core.tween
8 | import androidx.compose.foundation.ExperimentalFoundationApi
9 | import androidx.compose.foundation.horizontalScroll
10 | import androidx.compose.foundation.layout.Arrangement
11 | import androidx.compose.foundation.layout.Box
12 | import androidx.compose.foundation.layout.BoxScope
13 | import androidx.compose.foundation.layout.Column
14 | import androidx.compose.foundation.layout.Row
15 | import androidx.compose.foundation.layout.fillMaxSize
16 | import androidx.compose.foundation.layout.fillMaxWidth
17 | import androidx.compose.foundation.layout.height
18 | import androidx.compose.foundation.layout.padding
19 | import androidx.compose.foundation.layout.width
20 | import androidx.compose.foundation.pager.HorizontalPager
21 | import androidx.compose.foundation.pager.rememberPagerState
22 | import androidx.compose.foundation.rememberScrollState
23 | import androidx.compose.foundation.shape.CircleShape
24 | import androidx.compose.foundation.shape.RoundedCornerShape
25 | import androidx.compose.material.Card
26 | import androidx.compose.material.CircularProgressIndicator
27 | import androidx.compose.material3.MaterialTheme
28 | import androidx.compose.material3.Surface
29 | import androidx.compose.material3.Text
30 | import androidx.compose.runtime.Composable
31 | import androidx.compose.runtime.LaunchedEffect
32 | import androidx.compose.runtime.getValue
33 | import androidx.compose.runtime.mutableStateOf
34 | import androidx.compose.runtime.remember
35 | import androidx.compose.runtime.rememberCoroutineScope
36 | import androidx.compose.runtime.setValue
37 | import androidx.compose.ui.Alignment
38 | import androidx.compose.ui.Modifier
39 | import androidx.compose.ui.graphics.Color
40 | import androidx.compose.ui.graphics.painter.Painter
41 | import androidx.compose.ui.layout.ContentScale
42 | import androidx.compose.ui.text.TextStyle
43 | import androidx.compose.ui.text.style.TextAlign
44 | import androidx.compose.ui.unit.dp
45 | import androidx.compose.ui.unit.sp
46 | import io.kamel.core.Resource
47 | import io.kamel.image.KamelImage
48 | import io.kamel.image.asyncPainterResource
49 | import kotlinx.coroutines.delay
50 | import kotlinx.coroutines.launch
51 | import kotlinx.datetime.Clock
52 |
53 | @ExperimentalFoundationApi
54 | @Composable
55 | fun PromotionCardWithPager(promotions: List) {
56 | if (promotions.isNotEmpty()) {
57 | var currentPage by remember { mutableStateOf(0) }
58 | val scope = rememberCoroutineScope()
59 | val pagerState = rememberPagerState(pageCount = { promotions.size })
60 |
61 | val currentTimeMillis = Clock.System.now().toEpochMilliseconds()
62 | val activePromotions = promotions.take(8)
63 | LaunchedEffect(currentPage) {
64 | delay(1000)
65 | while (true) {
66 | delay(2000)
67 | if (activePromotions.isNotEmpty()) {
68 | val nextPage = (currentPage + 1) % activePromotions.size
69 | scope.launch {
70 | currentPage = nextPage
71 | pagerState.animateScrollToPage(nextPage)
72 | }
73 | }
74 | }
75 | }
76 |
77 | Column(
78 | modifier = Modifier
79 | .fillMaxWidth()
80 | .height(250.dp)
81 | .padding(16.dp)
82 | ) {
83 | HorizontalPager(
84 | modifier = Modifier
85 | .fillMaxWidth()
86 | .weight(1f),
87 | state = pagerState
88 | ) { page ->
89 | Card(
90 | modifier = Modifier
91 | .fillMaxSize()
92 | .padding(8.dp),
93 | elevation = 8.dp,
94 | shape = RoundedCornerShape(16.dp)
95 | ) {
96 | if (activePromotions.isNotEmpty() && page in activePromotions.indices) {
97 | val image: Resource =
98 | asyncPainterResource(data = activePromotions[page].imageurl)
99 | KamelImage(
100 | { image }, contentDescription = null,
101 | modifier = Modifier.fillMaxWidth(),
102 | contentScale = ContentScale.Crop,
103 | onLoading = {
104 | CircularProgressIndicator(progress = it)
105 | }, animationSpec = tween(
106 | durationMillis = 100,
107 | delayMillis = 100,
108 | easing = FastOutSlowInEasing
109 | )
110 | )
111 | } else {
112 | Box(
113 | modifier = Modifier.fillMaxSize(),
114 | contentAlignment = Alignment.Center
115 | ) {
116 | Text(
117 | text = "No promotions available",
118 | style = TextStyle(color = Color.Gray, fontSize = 16.sp),
119 | textAlign = TextAlign.Center
120 | )
121 | }
122 | }
123 | }
124 | }
125 |
126 | DotsIndicator(
127 | modifier = Modifier
128 | .padding(vertical = 8.dp)
129 | .align(Alignment.CenterHorizontally),
130 | pageCount = activePromotions.size,
131 | currentPage = currentPage,
132 | onPageSelected = { page ->
133 | scope.launch {
134 | currentPage = page
135 | pagerState.scrollToPage(page)
136 | }
137 | }
138 | )
139 | }
140 | }
141 | }
142 |
143 | @Composable
144 | fun DotsIndicator(
145 | modifier: Modifier = Modifier,
146 | pageCount: Int,
147 | currentPage: Int,
148 | onPageSelected: (Int) -> Unit,
149 | ) {
150 | Box(modifier = modifier) {
151 | Row(
152 | modifier = Modifier.horizontalScroll(rememberScrollState()),
153 | horizontalArrangement = Arrangement.spacedBy(8.dp),
154 | verticalAlignment = Alignment.CenterVertically
155 | ) {
156 | repeat(pageCount) { index ->
157 | Dot(
158 | isSelected = index == currentPage,
159 | onClick = {
160 | onPageSelected(index)
161 | }
162 | )
163 | }
164 | }
165 | }
166 | }
167 |
168 | @Composable
169 | fun Dot(
170 | isSelected: Boolean,
171 | onClick: () -> Unit,
172 | ) {
173 | val width by animateDpAsState(
174 | targetValue = if (isSelected) 16.dp else 6.dp,
175 | animationSpec = tween(
176 | durationMillis = 300,
177 | easing = LinearOutSlowInEasing
178 | )
179 | )
180 | val shape = if (isSelected) CircleShape else RoundedCornerShape(50)
181 | val color = if (isSelected) Color.Blue else Color.LightGray
182 |
183 | Surface(
184 | modifier = Modifier.width(width)
185 | .height(6.dp),
186 | shape = shape,
187 | color = color,
188 | onClick = onClick
189 | ) {}
190 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/components/SuggestionMessage.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.components
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.Row
9 | import androidx.compose.foundation.layout.Spacer
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.foundation.layout.width
14 | import androidx.compose.foundation.shape.CircleShape
15 | import androidx.compose.foundation.shape.RoundedCornerShape
16 | import androidx.compose.material3.Icon
17 | import androidx.compose.material3.Text
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.getValue
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.draw.shadow
23 | import androidx.compose.ui.graphics.Color
24 | import androidx.compose.ui.text.font.FontWeight
25 | import androidx.compose.ui.unit.dp
26 | import androidx.compose.ui.unit.sp
27 | import crypto_kmp.composeapp.generated.resources.Res
28 | import crypto_kmp.composeapp.generated.resources.thumbs_down
29 | import crypto_kmp.composeapp.generated.resources.thumbs_up
30 | import org.company.app.theme.LocalThemeIsDark
31 | import org.jetbrains.compose.resources.DrawableResource
32 | import org.jetbrains.compose.resources.painterResource
33 |
34 | @Composable
35 | fun SuggestionMessage() {
36 | val isDark by LocalThemeIsDark.current
37 | Column(
38 | modifier = Modifier.fillMaxWidth()
39 | .padding(all = 10.dp)
40 | .shadow(4.dp, shape = RoundedCornerShape(12.dp))
41 | .background(
42 | color = if (isDark) Color.DarkGray else Color.White,
43 | shape = RoundedCornerShape(12.dp)
44 | ),
45 | horizontalAlignment = Alignment.CenterHorizontally,
46 | verticalArrangement = Arrangement.Center
47 | ) {
48 | Column(
49 | modifier = Modifier.fillMaxWidth().padding(8.dp),
50 | horizontalAlignment = Alignment.CenterHorizontally,
51 | verticalArrangement = Arrangement.Center
52 | ) {
53 | Row(
54 | verticalAlignment = Alignment.CenterVertically,
55 | horizontalArrangement = Arrangement.Start
56 | ) {
57 | Column(
58 | horizontalAlignment = Alignment.Start,
59 | verticalArrangement = Arrangement.Center
60 | ) {
61 | Text(
62 | text = "How do you feel about the Crypto\n market today?",
63 | color = if (isDark) Color.White else Color.Black,
64 | fontSize = 14.sp,
65 | fontWeight = FontWeight.SemiBold
66 | )
67 | Text(
68 | text = "Vote to see results",
69 | color = Color.LightGray,
70 | fontSize = 12.sp,
71 | )
72 | }
73 | Spacer(modifier = Modifier.weight(1f))
74 | Row(
75 | horizontalArrangement = Arrangement.Center,
76 | verticalAlignment = Alignment.CenterVertically
77 | ) {
78 | ThumbsIcon(
79 | icon = Res.drawable.thumbs_down,
80 | )
81 | Spacer(modifier = Modifier.width(8.dp))
82 | ThumbsIcon(
83 | icon = Res.drawable.thumbs_up,
84 | )
85 |
86 | }
87 | }
88 |
89 | }
90 | }
91 | }
92 |
93 | @Composable
94 | fun ThumbsIcon(
95 | icon: DrawableResource,
96 | contentDes: String = "Thumbs",
97 | ) {
98 | Box(
99 | modifier = Modifier.size(40.dp)
100 | .background(color = Color.Gray, shape = CircleShape),
101 | contentAlignment = Alignment.Center
102 | ) {
103 | Image(
104 | modifier = Modifier.fillMaxWidth()
105 | .padding(6.dp),
106 | painter = painterResource(icon),
107 | contentDescription = contentDes,
108 | )
109 | }
110 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/navigation/rails/items/NavigationItem.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.navigation.rails.items
2 |
3 | import androidx.compose.ui.graphics.vector.ImageVector
4 |
5 | data class NavigationItem(
6 | val title: String,
7 | val unselectedIcon: ImageVector,
8 | val selectedIcon: ImageVector,
9 | val hasNews: Boolean,
10 | val badgeCount: Int? = null,
11 | )
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/navigation/rails/navbar/NavigationSideBar.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.navigation.rails.navbar
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxHeight
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.filled.Menu
11 | import androidx.compose.material3.Badge
12 | import androidx.compose.material3.BadgedBox
13 | import androidx.compose.material3.Icon
14 | import androidx.compose.material3.IconButton
15 | import androidx.compose.material3.MaterialTheme
16 | import androidx.compose.material3.NavigationRail
17 | import androidx.compose.material3.NavigationRailItem
18 | import androidx.compose.material3.Text
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.getValue
21 | import androidx.compose.runtime.mutableStateOf
22 | import androidx.compose.runtime.remember
23 | import androidx.compose.runtime.setValue
24 | import androidx.compose.ui.Alignment
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.draw.clip
27 | import androidx.compose.ui.unit.dp
28 | import org.company.app.presentation.ui.navigation.rails.items.NavigationItem
29 |
30 | @Composable
31 | fun NavigationSideBar(
32 | items: List, selectedItemIndex: Int, onNavigate: (Int) -> Unit,
33 | ) {
34 | var isTitleVisible by remember { mutableStateOf(false) }
35 | NavigationRail(
36 | header = {
37 | IconButton(onClick = {
38 | isTitleVisible = !isTitleVisible
39 | }) {
40 | Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
41 | }
42 | },
43 | modifier = Modifier.height(400.dp)
44 | .clip(RoundedCornerShape(40.dp))
45 | ) {
46 | Column(
47 | modifier = Modifier.fillMaxHeight(),
48 | verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically)
49 | ) {
50 | items.forEachIndexed { index, item ->
51 | NavigationRailItem(
52 | selected = selectedItemIndex == index,
53 | onClick = {
54 | onNavigate(index)
55 | },
56 | icon = {
57 | NavigationIcon(
58 | item = item, selected = selectedItemIndex == index
59 | )
60 | },
61 | label = {
62 | AnimatedVisibility(isTitleVisible) {
63 | Text(
64 | text = item.title,
65 | fontSize = MaterialTheme.typography.bodySmall.fontSize
66 | )
67 | }
68 | }
69 | )
70 | }
71 | }
72 | }
73 | }
74 |
75 | @Composable
76 | fun NavigationIcon(
77 | item: NavigationItem, selected: Boolean,
78 | ) {
79 | BadgedBox(badge = {
80 | if (item.badgeCount != null) {
81 | Badge {
82 | Text(text = item.badgeCount.toString())
83 | }
84 | } else if (item.hasNews) {
85 | Badge()
86 | }
87 | }) {
88 | Icon(
89 | imageVector = if (selected) item.selectedIcon else item.unselectedIcon,
90 | contentDescription = item.title
91 | )
92 | }
93 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/navigation/tab/analytics/Analytics.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.navigation.tab.analytics
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Analytics
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
10 | import cafe.adriel.voyager.navigator.Navigator
11 | import cafe.adriel.voyager.navigator.tab.Tab
12 | import cafe.adriel.voyager.navigator.tab.TabOptions
13 | import org.company.app.presentation.ui.screens.analytics.AnalyticScreen
14 |
15 | object Analytics : Tab {
16 | @Composable
17 | override fun Content() {
18 | Navigator(AnalyticScreen())
19 | }
20 |
21 | override val options: TabOptions
22 | @Composable
23 | get() {
24 | val icon = rememberVectorPainter(Icons.Default.Analytics)
25 | val title by remember { mutableStateOf("Analytics") }
26 | val index: UShort = 1u
27 | return TabOptions(index, title, icon)
28 | }
29 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/navigation/tab/home/Home.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.navigation.tab.home
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Home
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
10 | import cafe.adriel.voyager.navigator.Navigator
11 | import cafe.adriel.voyager.navigator.tab.Tab
12 | import cafe.adriel.voyager.navigator.tab.TabOptions
13 | import org.company.app.presentation.ui.screens.home.HomeScreen
14 |
15 | object Home : Tab {
16 | @Composable
17 | override fun Content() {
18 | Navigator(HomeScreen())
19 | }
20 |
21 | override val options: TabOptions
22 | @Composable
23 | get() {
24 | val icon = rememberVectorPainter(Icons.Default.Home)
25 | val title by remember { mutableStateOf("Home") }
26 | val index: UShort = 0u
27 | return TabOptions(index, title, icon)
28 | }
29 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/navigation/tab/news/News.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.navigation.tab.news
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Newspaper
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
10 | import cafe.adriel.voyager.navigator.Navigator
11 | import cafe.adriel.voyager.navigator.tab.Tab
12 | import cafe.adriel.voyager.navigator.tab.TabOptions
13 | import org.company.app.presentation.ui.screens.news.NewsScreen
14 |
15 | object News : Tab {
16 | @Composable
17 | override fun Content() {
18 | Navigator(NewsScreen())
19 | }
20 |
21 | override val options: TabOptions
22 | @Composable
23 | get() {
24 | val icon = rememberVectorPainter(Icons.Default.Newspaper)
25 | val title by remember { mutableStateOf("News") }
26 | val index: UShort = 2u
27 | return TabOptions(index, title, icon)
28 | }
29 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/navigation/tab/profile/Profile.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.navigation.tab.profile
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Person
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
10 | import cafe.adriel.voyager.navigator.Navigator
11 | import cafe.adriel.voyager.navigator.tab.Tab
12 | import cafe.adriel.voyager.navigator.tab.TabOptions
13 | import org.company.app.presentation.ui.screens.profile.ProfileScreen
14 |
15 | object Profile : Tab {
16 | @Composable
17 | override fun Content() {
18 | Navigator(ProfileScreen())
19 | }
20 |
21 | override val options: TabOptions
22 | @Composable
23 | get() {
24 | val icon = rememberVectorPainter(Icons.Default.Person)
25 | val title by remember { mutableStateOf("Profile") }
26 | val index: UShort = 3u
27 | return TabOptions(index, title, icon)
28 | }
29 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/screens/detail/DetailScreen.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.screens.detail
2 |
3 | import Notification
4 | import NotificationType
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.Arrangement
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.Row
9 | import androidx.compose.foundation.layout.Spacer
10 | import androidx.compose.foundation.layout.WindowInsets
11 | import androidx.compose.foundation.layout.fillMaxWidth
12 | import androidx.compose.foundation.layout.height
13 | import androidx.compose.foundation.layout.padding
14 | import androidx.compose.foundation.layout.size
15 | import androidx.compose.foundation.layout.statusBars
16 | import androidx.compose.foundation.layout.width
17 | import androidx.compose.foundation.layout.windowInsetsPadding
18 | import androidx.compose.foundation.rememberScrollState
19 | import androidx.compose.foundation.shape.RoundedCornerShape
20 | import androidx.compose.foundation.verticalScroll
21 | import androidx.compose.material.icons.Icons
22 | import androidx.compose.material.icons.outlined.ArrowBackIosNew
23 | import androidx.compose.material.icons.outlined.Notifications
24 | import androidx.compose.material.icons.outlined.StarOutline
25 | import androidx.compose.material3.ButtonDefaults
26 | import androidx.compose.material3.Card
27 | import androidx.compose.material3.CenterAlignedTopAppBar
28 | import androidx.compose.material3.ExperimentalMaterial3Api
29 | import androidx.compose.material3.HorizontalDivider
30 | import androidx.compose.material3.Icon
31 | import androidx.compose.material3.MaterialTheme
32 | import androidx.compose.material3.Scaffold
33 | import androidx.compose.material3.Tab
34 | import androidx.compose.material3.TabRow
35 | import androidx.compose.material3.TabRowDefaults
36 | import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
37 | import androidx.compose.material3.Text
38 | import androidx.compose.material3.TextButton
39 | import androidx.compose.runtime.Composable
40 | import androidx.compose.runtime.getValue
41 | import androidx.compose.runtime.mutableStateOf
42 | import androidx.compose.runtime.remember
43 | import androidx.compose.runtime.setValue
44 | import androidx.compose.ui.Alignment
45 | import androidx.compose.ui.Modifier
46 | import androidx.compose.ui.graphics.Color
47 | import androidx.compose.ui.text.SpanStyle
48 | import androidx.compose.ui.text.buildAnnotatedString
49 | import androidx.compose.ui.text.font.FontWeight
50 | import androidx.compose.ui.text.style.TextAlign
51 | import androidx.compose.ui.text.style.TextOverflow
52 | import androidx.compose.ui.text.withStyle
53 | import androidx.compose.ui.unit.dp
54 | import androidx.compose.ui.unit.sp
55 | import cafe.adriel.voyager.core.screen.Screen
56 | import cafe.adriel.voyager.navigator.LocalNavigator
57 | import createNotification
58 | import org.company.app.domain.model.crypto.Data
59 | import org.company.app.presentation.ui.components.CryptoChart
60 | import org.company.app.presentation.ui.components.CurrencyImage
61 | import org.company.app.presentation.ui.components.MarketDataRow
62 | import org.company.app.theme.LocalThemeIsDark
63 | import org.company.app.utils.formatMarketCap
64 | import kotlin.math.roundToInt
65 |
66 | class DetailScreen(
67 | private val data: Data,
68 | ) : Screen {
69 | @Composable
70 | override fun Content() {
71 | DetailContent(data)
72 | }
73 | }
74 |
75 | @OptIn(ExperimentalMaterial3Api::class)
76 | @Composable
77 | fun DetailContent(data: Data) {
78 | val notification = createNotification(NotificationType.TOP)
79 | var selectedPeriod by remember { mutableStateOf("1H") }
80 | val capList = remember { mutableListOf("1H", "1D", "1W", "1M", "3M", "6M", "1Y") }
81 | val isDark by LocalThemeIsDark.current
82 | val navigator = LocalNavigator.current
83 | val textColor = if (isDark) Color.White else Color.Black
84 | val percentChange24h = data.quote.uSD.percentChange24h
85 | val capMarket = data.quote.uSD.percentChange30d
86 | val textColor24h = if (percentChange24h > 0) Color.Green else Color.Red
87 | val textColor1h = if (capMarket > 0) Color.Green else Color.Red
88 | Scaffold(
89 | topBar = {
90 | CenterAlignedTopAppBar(
91 | title = {
92 | Row(
93 | modifier = Modifier.fillMaxWidth(),
94 | horizontalArrangement = Arrangement.Center,
95 | verticalAlignment = Alignment.CenterVertically
96 | ) {
97 | CurrencyImage(
98 | id = data.id,
99 | modifier = Modifier.size(30.dp)
100 | )
101 | Spacer(modifier = Modifier.width(6.dp))
102 | Text(
103 | "${data.name}",
104 | textAlign = TextAlign.Center,
105 | fontSize = MaterialTheme.typography.titleLarge.fontSize,
106 | maxLines = 1,
107 | overflow = TextOverflow.Ellipsis
108 | )
109 | }
110 | },
111 | navigationIcon = {
112 | Icon(
113 | imageVector = Icons.Outlined.ArrowBackIosNew,
114 | contentDescription = "Menu Icon",
115 | modifier = Modifier.clickable {
116 | navigator?.pop()
117 | }
118 | )
119 | },
120 | actions = {
121 | Icon(
122 | imageVector = Icons.Outlined.Notifications,
123 | contentDescription = "Notifications Icon",
124 | modifier = Modifier.clickable {
125 | notification.show(
126 | message ="CMC Rank:"+data.cmcRank.toString() +"Name:"+data.name + "Currency:" +data.quote.uSD,
127 | title = data.name,
128 | duration = NotificationDuration.SHORT
129 | )
130 | }
131 | )
132 | Icon(
133 | imageVector = Icons.Outlined.StarOutline,
134 | contentDescription = "Favourite Icon"
135 | )
136 | },
137 | modifier = Modifier.fillMaxWidth()
138 | .windowInsetsPadding(WindowInsets.statusBars)
139 | )
140 | }
141 | ) { paddingValues->
142 | Column(
143 | modifier = Modifier
144 | .fillMaxWidth()
145 | .padding(top = paddingValues.calculateTopPadding())
146 | .verticalScroll(rememberScrollState()),
147 | horizontalAlignment = Alignment.Start,
148 | verticalArrangement = Arrangement.Center
149 | ) {
150 | Row(
151 | modifier = Modifier.fillMaxWidth()
152 | .padding(8.dp),
153 | horizontalArrangement = Arrangement.Start,
154 | verticalAlignment = Alignment.CenterVertically
155 | ) {
156 | CurrencyImage(
157 | id = data.id,
158 | modifier = Modifier.size(30.dp)
159 | )
160 | Spacer(modifier = Modifier.width(6.dp))
161 | Text(
162 | text = data.name,
163 | fontSize = MaterialTheme.typography.titleLarge.fontSize,
164 | fontWeight = FontWeight.Bold,
165 | )
166 | }
167 | Spacer(modifier = Modifier.height(16.dp))
168 | Row(
169 | modifier = Modifier.fillMaxWidth()
170 | .padding(8.dp),
171 | horizontalArrangement = Arrangement.SpaceBetween,
172 | verticalAlignment = Alignment.CenterVertically
173 | ) {
174 | val priceString = buildAnnotatedString {
175 | withStyle(
176 | SpanStyle(
177 | color = Color.Gray,
178 | fontSize = 20.sp,
179 | fontWeight = FontWeight.Bold
180 | )
181 | ) {
182 | append("$ ")
183 | }
184 | withStyle(
185 | SpanStyle(
186 | color = textColor,
187 | fontSize = 24.sp,
188 | fontWeight = FontWeight.Bold
189 | )
190 | ) {
191 | append("$" + "${((data.quote.uSD.price * 100).roundToInt()) / 100.0}")
192 | }
193 | withStyle(
194 | SpanStyle(
195 | color = Color.Gray,
196 | fontSize = 20.sp,
197 | fontWeight = FontWeight.Bold
198 | )
199 | ) {
200 | append(" USD")
201 | }
202 |
203 | }
204 | Text(
205 | text = priceString
206 | )
207 | Text(
208 | text = "${percentChange24h.roundToInt()}%",
209 | color = textColor24h,
210 | fontSize = 20.sp
211 | )
212 | }
213 | Row(
214 | modifier = Modifier.fillMaxWidth()
215 | .padding(8.dp),
216 | horizontalArrangement = Arrangement.Center,
217 | verticalAlignment = Alignment.CenterVertically
218 | ) {
219 | CurrencyImage(
220 | id = data.id,
221 | modifier = Modifier.size(20.dp)
222 | )
223 | Spacer(modifier = Modifier.width(6.dp))
224 | val latestCap = buildAnnotatedString {
225 | withStyle(
226 | SpanStyle(
227 | color = Color.Gray,
228 | fontSize = 16.sp,
229 | fontWeight = FontWeight.Bold
230 | )
231 | ) {
232 | append("${data.quote.uSD.fullyDilutedMarketCap}")
233 | }
234 | withStyle(
235 | SpanStyle(
236 | color = Color.Gray,
237 | fontSize = 16.sp,
238 | fontWeight = FontWeight.Bold
239 | )
240 | ) {
241 | append(" ${data.symbol}")
242 | }
243 |
244 | }
245 | Text(
246 | text = latestCap
247 | )
248 | Spacer(modifier = Modifier.weight(1f))
249 | Text(
250 | text = "${capMarket.roundToInt()}%",
251 | color = textColor1h,
252 | fontSize = 16.sp
253 | )
254 | }
255 | Spacer(modifier = Modifier.height(16.dp))
256 | TabRow(
257 | selectedTabIndex = capList.indexOf(selectedPeriod),
258 | containerColor = if (isDark) MaterialTheme.colorScheme.surface else Color.White,
259 | contentColor = if (isDark) Color.White else Color.Black,
260 | indicator = { tabPositions ->
261 | TabRowDefaults.Indicator(
262 | modifier = Modifier.tabIndicatorOffset(
263 | tabPositions[capList.indexOf(
264 | selectedPeriod
265 | )]
266 | )
267 | )
268 | },
269 | modifier = Modifier.fillMaxWidth()
270 | ) {
271 | capList.forEachIndexed { index, period ->
272 | Tab(
273 | selected = selectedPeriod == period,
274 | onClick = { selectedPeriod = period },
275 | text = { Text(text = period) },
276 | )
277 | }
278 | }
279 | Column(
280 | modifier = Modifier.fillMaxWidth()
281 | .height(330.dp),
282 | horizontalAlignment = Alignment.CenterHorizontally,
283 | verticalArrangement = Arrangement.Center
284 | ) {
285 | when (selectedPeriod) {
286 | "1H" -> {
287 | CryptoChart(data, "1H")
288 | }
289 |
290 | "1D" -> {
291 | CryptoChart(data, "1D")
292 |
293 | }
294 |
295 | "1W" -> {
296 | CryptoChart(data, "1W")
297 | }
298 |
299 | "1M" -> {
300 | CryptoChart(data, "1M")
301 | }
302 |
303 | "3M" -> {
304 | CryptoChart(data, "3M")
305 | }
306 |
307 | "6M" -> {
308 | CryptoChart(data, "6M")
309 | }
310 |
311 | "1Y" -> {
312 | CryptoChart(data, "1Y")
313 | }
314 |
315 | }
316 | }
317 | Spacer(modifier = Modifier.height(8.dp))
318 | MarketData(data, isDark)
319 | Spacer(modifier = Modifier.height(24.dp))
320 | BuyContent(data)
321 | }
322 |
323 | }
324 | }
325 |
326 | @Composable
327 | fun BuyContent(
328 | data: Data,
329 | ) {
330 | Card(
331 | modifier = Modifier.fillMaxWidth()
332 | .height(140.dp),
333 | shape = RoundedCornerShape(
334 | bottomStart = 12.dp, bottomEnd = 12.dp
335 | )
336 | ) {
337 | Column(
338 | modifier = Modifier.fillMaxWidth()
339 | .padding(12.dp),
340 | horizontalAlignment = Alignment.Start,
341 | verticalArrangement = Arrangement.Center
342 | ) {
343 | Spacer(modifier = Modifier.weight(1f))
344 |
345 | Row(
346 | modifier = Modifier.fillMaxWidth(),
347 | verticalAlignment = Alignment.CenterVertically,
348 | horizontalArrangement = Arrangement.Start
349 | ) {
350 | CurrencyImage(
351 | id = data.id,
352 | modifier = Modifier.size(30.dp)
353 | )
354 | Spacer(modifier = Modifier.width(10.dp))
355 | Text(
356 | text = data.name,
357 | fontSize = MaterialTheme.typography.titleLarge.fontSize,
358 | fontWeight = FontWeight.Bold,
359 | )
360 | Spacer(modifier = Modifier.weight(1f))
361 | Text(
362 | text = "0 ${data.symbol}",
363 | fontSize = MaterialTheme.typography.titleLarge.fontSize,
364 | fontWeight = FontWeight.Bold,
365 | )
366 | }
367 | Spacer(modifier = Modifier.weight(1f))
368 | TextButton(
369 | onClick = {
370 |
371 | },
372 | modifier = Modifier.fillMaxWidth(),
373 | colors = ButtonDefaults.buttonColors(
374 | containerColor = Color(0xFF9234eb),
375 | contentColor = Color.White
376 | ),
377 | shape = RoundedCornerShape(8.dp),
378 | ) {
379 | Text(
380 | "Buy ${data.symbol}",
381 | fontSize = MaterialTheme.typography.titleMedium.fontSize
382 | )
383 | }
384 | }
385 | }
386 | }
387 |
388 | @Composable
389 | fun MarketData(
390 | data: Data,
391 | isDark: Boolean,
392 | ) {
393 | Column(
394 | modifier = Modifier.fillMaxWidth(),
395 | verticalArrangement = Arrangement.Center,
396 | horizontalAlignment = Alignment.Start
397 | ) {
398 | Text(
399 | text = "MARKET DATA",
400 | fontSize = MaterialTheme.typography.titleMedium.fontSize,
401 | fontWeight = FontWeight.Bold,
402 | color = if (isDark) Color.White else Color.Black,
403 | modifier = Modifier.padding(start = 8.dp)
404 | )
405 | Spacer(modifier = Modifier.height(16.dp))
406 | HorizontalDivider(color = Color.LightGray)
407 | Spacer(modifier = Modifier.height(6.dp))
408 | Row(
409 | modifier = Modifier.fillMaxWidth(),
410 | horizontalArrangement = Arrangement.SpaceAround,
411 | verticalAlignment = Alignment.CenterVertically
412 | ) {
413 | MarketDataRow("MARKET CAP", formatMarketCap(data.quote.uSD.marketCap), isDark)
414 | MarketDataRow("24H VOLUME", formatMarketCap(data.quote.uSD.volume24h), isDark)
415 | MarketDataRow("RANK", "#${data.cmcRank}", isDark)
416 | }
417 | }
418 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/screens/home/HomeContent.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.screens.home
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.WindowInsets
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.safeDrawing
14 | import androidx.compose.foundation.layout.statusBars
15 | import androidx.compose.foundation.layout.windowInsetsPadding
16 | import androidx.compose.foundation.rememberScrollState
17 | import androidx.compose.foundation.shape.RoundedCornerShape
18 | import androidx.compose.foundation.verticalScroll
19 | import androidx.compose.material.ExperimentalMaterialApi
20 | import androidx.compose.material.TopAppBar
21 | import androidx.compose.material.icons.Icons
22 | import androidx.compose.material.icons.filled.Search
23 | import androidx.compose.material.icons.outlined.Notifications
24 | import androidx.compose.material.icons.outlined.WbSunny
25 | import androidx.compose.material.pullrefresh.PullRefreshIndicator
26 | import androidx.compose.material.pullrefresh.pullRefresh
27 | import androidx.compose.material.pullrefresh.rememberPullRefreshState
28 | import androidx.compose.material3.CenterAlignedTopAppBar
29 | import androidx.compose.material3.ExperimentalMaterial3Api
30 | import androidx.compose.material3.Icon
31 | import androidx.compose.material3.MaterialTheme
32 | import androidx.compose.material3.Scaffold
33 | import androidx.compose.material3.Text
34 | import androidx.compose.material3.TextField
35 | import androidx.compose.material3.TextFieldDefaults
36 | import androidx.compose.runtime.Composable
37 | import androidx.compose.runtime.LaunchedEffect
38 | import androidx.compose.runtime.collectAsState
39 | import androidx.compose.runtime.getValue
40 | import androidx.compose.runtime.mutableStateOf
41 | import androidx.compose.runtime.remember
42 | import androidx.compose.runtime.rememberCoroutineScope
43 | import androidx.compose.runtime.setValue
44 | import androidx.compose.ui.Alignment
45 | import androidx.compose.ui.Modifier
46 | import androidx.compose.ui.graphics.Color
47 | import androidx.compose.ui.text.font.FontWeight
48 | import androidx.compose.ui.text.style.TextAlign
49 | import androidx.compose.ui.unit.dp
50 | import cafe.adriel.voyager.navigator.LocalNavigator
51 | import kotlinx.coroutines.delay
52 | import kotlinx.coroutines.launch
53 | import org.company.app.domain.model.crypto.Data
54 | import org.company.app.domain.model.crypto.LatestListing
55 | import org.company.app.domain.usecase.ResultState
56 | import org.company.app.presentation.ui.components.CryptoList
57 | import org.company.app.presentation.ui.components.ErrorBox
58 | import org.company.app.presentation.ui.components.LoadingBox
59 | import org.company.app.presentation.ui.components.SuggestionMessage
60 | import org.company.app.presentation.viewmodel.MainViewModel
61 | import org.company.app.theme.LocalThemeIsDark
62 | import org.koin.compose.koinInject
63 |
64 | @OptIn(ExperimentalMaterialApi::class)
65 | @Composable
66 | fun HomeContent(
67 | viewModel: MainViewModel = koinInject(),
68 | ) {
69 | var isDark by LocalThemeIsDark.current
70 | var listingData by remember { mutableStateOf(null) }
71 | var queryText by remember { mutableStateOf("") }
72 | val refreshScope = rememberCoroutineScope()
73 | var refreshing by remember { mutableStateOf(false) }
74 | fun refresh() {
75 | refreshScope.launch {
76 | viewModel.getLatestListing()
77 | delay(1500)
78 | refreshing = false
79 | }
80 | }
81 |
82 | val refreshState = rememberPullRefreshState(refreshing, ::refresh)
83 | LaunchedEffect(Unit) {
84 | viewModel.getLatestListing()
85 | }
86 | val latestState by viewModel.latestListing.collectAsState()
87 | when (latestState) {
88 | is ResultState.ERROR -> {
89 | val error = (latestState as ResultState.ERROR).message
90 | ErrorBox(error)
91 | }
92 |
93 | is ResultState.LOADING -> {
94 | LoadingBox()
95 | }
96 |
97 | is ResultState.SUCCESS -> {
98 | val data = (latestState as ResultState.SUCCESS).response
99 | listingData = data
100 | }
101 | }
102 | Box(
103 | modifier = Modifier
104 | .fillMaxWidth()
105 | .pullRefresh(state = refreshState),
106 | contentAlignment = Alignment.Center
107 | ) {
108 | Column(
109 | modifier = Modifier
110 | .fillMaxWidth()
111 | .windowInsetsPadding(WindowInsets.statusBars)
112 | .verticalScroll(rememberScrollState()),
113 | horizontalAlignment = Alignment.CenterHorizontally,
114 | verticalArrangement = Arrangement.Center
115 | ) {
116 | Row(
117 | modifier = Modifier.fillMaxWidth()
118 | .padding(3.dp),
119 | horizontalArrangement = Arrangement.SpaceBetween,
120 | verticalAlignment = Alignment.CenterVertically
121 | ) {
122 |
123 | Text(
124 | "Crypto Coins",
125 | textAlign = TextAlign.Center,
126 | fontSize = MaterialTheme.typography.titleLarge.fontSize,
127 | fontWeight = FontWeight.Bold
128 | )
129 |
130 | Spacer(modifier = Modifier.weight(1f))
131 |
132 | Icon(
133 | imageVector = Icons.Outlined.WbSunny,
134 | contentDescription = "Menu Icon",
135 | modifier = Modifier.clickable {
136 | isDark = !isDark
137 | }
138 | )
139 |
140 | Icon(
141 | imageVector = Icons.Outlined.Notifications,
142 | contentDescription = "Menu Icon"
143 | )
144 | }
145 | TextField(
146 | value = queryText,
147 | onValueChange = {
148 | queryText = it
149 | },
150 | placeholder = { Text(text = "Search Coins") },
151 | leadingIcon = {
152 | Icon(
153 | imageVector = Icons.Default.Search,
154 | contentDescription = null,
155 | modifier = Modifier.padding(horizontal = 12.dp)
156 | )
157 | },
158 | modifier = Modifier
159 | .fillMaxWidth()
160 | .padding(10.dp),
161 | colors = TextFieldDefaults.colors(
162 | focusedTextColor = if (isDark) Color.White else Color.Black,
163 | unfocusedTextColor = if (isDark) Color.White else Color.LightGray,
164 | focusedIndicatorColor = Color.Transparent,
165 | unfocusedIndicatorColor = Color.Transparent
166 | ),
167 | shape = RoundedCornerShape(40.dp)
168 | )
169 | listingData?.data?.let { dataList ->
170 | if (queryText.isEmpty()) {
171 | if (dataList.isNotEmpty()) {
172 | val combinedFilteredList = mutableListOf()
173 | if (dataList.size > 0) {
174 | combinedFilteredList.add(dataList[0])
175 | }
176 | if (dataList.size > 12) {
177 | combinedFilteredList.add(dataList[12])
178 | }
179 |
180 | CryptoList(
181 | dataList = combinedFilteredList,
182 | coinsText = "Favourite",
183 | viewText = "Large Cap",
184 | largeCapColor = Color(0xFFc127d9),
185 | isCapIconEnabled = true
186 | )
187 | Spacer(modifier = Modifier.height(8.dp))
188 | SuggestionMessage()
189 | Spacer(modifier = Modifier.height(8.dp))
190 | CryptoList(
191 | dataList = dataList,
192 | coinsText = "All Coins",
193 | viewText = "View All"
194 | )
195 | } else {
196 | Text(text = "No data available.")
197 | }
198 | } else {
199 | val filteredList =
200 | dataList.filter { it.name.contains(queryText, ignoreCase = true) }
201 | if (filteredList.isNotEmpty()) {
202 | CryptoList(
203 | dataList = filteredList,
204 | coinsText = "Search Results",
205 | viewText = "View All"
206 | )
207 | } else {
208 | Text(text = "No matching results.")
209 | }
210 | }
211 |
212 | }
213 |
214 | }
215 | PullRefreshIndicator(
216 | refreshing = refreshing,
217 | state = refreshState,
218 | modifier = Modifier
219 | .align(Alignment.TopCenter)
220 | )
221 | }
222 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/screens/home/HomeScreen.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.screens.home
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.screen.Screen
5 |
6 | class HomeScreen : Screen {
7 | @Composable
8 | override fun Content() {
9 | HomeContent()
10 | }
11 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/screens/news/NewsScreen.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.screens.news
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.horizontalScroll
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.PaddingValues
11 | import androidx.compose.foundation.layout.Row
12 | import androidx.compose.foundation.layout.Spacer
13 | import androidx.compose.foundation.layout.WindowInsets
14 | import androidx.compose.foundation.layout.fillMaxSize
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.height
17 | import androidx.compose.foundation.layout.padding
18 | import androidx.compose.foundation.layout.size
19 | import androidx.compose.foundation.layout.statusBars
20 | import androidx.compose.foundation.layout.width
21 | import androidx.compose.foundation.layout.windowInsetsPadding
22 | import androidx.compose.foundation.lazy.LazyColumn
23 | import androidx.compose.foundation.lazy.LazyRow
24 | import androidx.compose.foundation.lazy.grid.GridCells
25 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
26 | import androidx.compose.foundation.lazy.grid.items
27 | import androidx.compose.foundation.lazy.items
28 | import androidx.compose.foundation.pager.HorizontalPager
29 | import androidx.compose.foundation.pager.PageSize
30 | import androidx.compose.foundation.pager.PagerState
31 | import androidx.compose.foundation.pager.rememberPagerState
32 | import androidx.compose.foundation.rememberScrollState
33 | import androidx.compose.foundation.shape.CircleShape
34 | import androidx.compose.foundation.shape.RoundedCornerShape
35 | import androidx.compose.material3.Card
36 | import androidx.compose.material3.CardDefaults
37 | import androidx.compose.material3.Divider
38 | import androidx.compose.material3.MaterialTheme
39 | import androidx.compose.material3.Surface
40 | import androidx.compose.material3.Text
41 | import androidx.compose.runtime.Composable
42 | import androidx.compose.runtime.LaunchedEffect
43 | import androidx.compose.runtime.collectAsState
44 | import androidx.compose.runtime.getValue
45 | import androidx.compose.runtime.mutableStateOf
46 | import androidx.compose.runtime.remember
47 | import androidx.compose.runtime.setValue
48 | import androidx.compose.ui.Alignment
49 | import androidx.compose.ui.Modifier
50 | import androidx.compose.ui.draw.clip
51 | import androidx.compose.ui.graphics.Brush
52 | import androidx.compose.ui.graphics.Color
53 | import androidx.compose.ui.graphics.painter.Painter
54 | import androidx.compose.ui.layout.ContentScale
55 | import androidx.compose.ui.text.style.TextOverflow
56 | import androidx.compose.ui.unit.Dp
57 | import androidx.compose.ui.unit.dp
58 | import cafe.adriel.voyager.core.screen.Screen
59 | import cafe.adriel.voyager.navigator.LocalNavigator
60 | import coil3.compose.AsyncImage
61 | import io.kamel.core.Resource
62 | import io.kamel.image.KamelImage
63 | import io.kamel.image.asyncPainterResource
64 | import kotlinx.coroutines.delay
65 | import org.company.app.domain.model.news.Data
66 | import org.company.app.domain.model.news.NewsList
67 | import org.company.app.domain.usecase.ResultState
68 | import org.company.app.presentation.ui.components.ErrorBox
69 | import org.company.app.presentation.ui.components.LoadingBox
70 | import org.company.app.presentation.ui.components.NewsDetailScreen
71 | import org.company.app.presentation.ui.components.PromotionCardWithPager
72 | import org.company.app.presentation.viewmodel.MainViewModel
73 | import org.company.app.utils.formatTimestamp
74 | import org.koin.compose.koinInject
75 | import kotlin.math.min
76 |
77 | class NewsScreen : Screen {
78 | @Composable
79 | override fun Content() {
80 | NewsContent()
81 | }
82 | }
83 |
84 | @Composable
85 | fun NewsContent(
86 | viewModel: MainViewModel = koinInject(),
87 | ) {
88 | var newsList by remember { mutableStateOf(null) }
89 |
90 | LaunchedEffect(Unit) {
91 | viewModel.getAllNews()
92 | }
93 |
94 | val newsState by viewModel.allNews.collectAsState()
95 | when (newsState) {
96 | is ResultState.ERROR -> {
97 | val error = (newsState as ResultState.ERROR).message
98 | ErrorBox(error)
99 | }
100 |
101 | ResultState.LOADING -> {
102 | LoadingBox()
103 | }
104 |
105 | is ResultState.SUCCESS -> {
106 | val response = (newsState as ResultState.SUCCESS).response
107 | newsList = response
108 | }
109 | }
110 |
111 | LazyColumn(
112 | modifier = Modifier.fillMaxSize()
113 | .windowInsetsPadding(WindowInsets.statusBars)
114 | ) {
115 | item {
116 | HeaderSection(newsList?.data)
117 | }
118 | item {
119 | Spacer(modifier = Modifier.height(16.dp))
120 | }
121 | item {
122 | LazyVerticalGrid(
123 | columns = GridCells.Adaptive(300.dp),
124 | horizontalArrangement = Arrangement.spacedBy(8.dp),
125 | verticalArrangement = Arrangement.spacedBy(8.dp),
126 | modifier = Modifier.fillMaxWidth()
127 | .height(900.dp)
128 | ) {
129 | newsList?.data?.let { listData ->
130 | items(listData) {
131 | NewsItemView(it)
132 | }
133 | }
134 | }
135 | }
136 | }
137 | }
138 |
139 | @OptIn(ExperimentalFoundationApi::class)
140 | @Composable
141 | fun HeaderSection(newsList: List?, modifier: Modifier = Modifier) {
142 | Column(modifier = modifier.padding(horizontal = 16.dp)) {
143 | Spacer(modifier = Modifier.height(16.dp))
144 |
145 | if (!newsList.isNullOrEmpty()) {
146 | val itemsToDisplay = min(8, newsList.size)
147 | val shuffledNewsList = remember { newsList.shuffled() }
148 |
149 | if (itemsToDisplay > 0) {
150 | Box(modifier = Modifier.fillMaxWidth()) {
151 | PromotionCardWithPager(shuffledNewsList)
152 | }
153 | }
154 | }
155 |
156 | Spacer(modifier = Modifier.height(16.dp))
157 |
158 | QuickFilters()
159 | }
160 | }
161 |
162 | @Composable
163 | fun QuickFilters(
164 | viewModel: MainViewModel = koinInject()
165 | ) {
166 | LaunchedEffect(Unit) {
167 | viewModel.getNewsCategories()
168 | }
169 | val newsCategories by viewModel.newsCategories.collectAsState()
170 |
171 | when (newsCategories) {
172 | is ResultState.ERROR -> {
173 | val error = (newsCategories as ResultState.ERROR).message
174 | ErrorBox(error)
175 | }
176 |
177 | ResultState.LOADING -> {
178 | // LoadingBox()
179 | }
180 |
181 | is ResultState.SUCCESS -> {
182 | val response = (newsCategories as ResultState.SUCCESS).response
183 | LazyRow(
184 | horizontalArrangement = Arrangement.spacedBy(8.dp),
185 | contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
186 | ) {
187 | items(response) { filter ->
188 | Chip(text = filter.categoryName)
189 | }
190 | }
191 | }
192 | }
193 |
194 | }
195 |
196 |
197 | @Composable
198 | fun Chip(text: String) {
199 | Surface(
200 | shape = RoundedCornerShape(16.dp),
201 | color = MaterialTheme.colorScheme.primary,
202 | contentColor = Color.White,
203 | modifier = Modifier.clickable { }
204 | ) {
205 | Text(
206 | text = text,
207 | modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
208 | )
209 | }
210 | }
211 |
212 | @Composable
213 | fun NewsItemView(news: Data) {
214 | val navigator = LocalNavigator.current
215 | Card(
216 | modifier = Modifier
217 | .fillMaxWidth()
218 | .padding(8.dp)
219 | .clickable { navigator?.push(NewsDetailScreen(news)) },
220 | shape = RoundedCornerShape(16.dp),
221 | elevation = CardDefaults.cardElevation(8.dp),
222 | colors = CardDefaults.cardColors(
223 | containerColor = MaterialTheme.colorScheme.surface
224 | )
225 | ) {
226 | Column(modifier = Modifier.padding(16.dp)) {
227 | val image: Resource = asyncPainterResource(news.imageurl)
228 | Box(
229 | modifier = Modifier
230 | .height(200.dp)
231 | .fillMaxWidth()
232 | .clip(RoundedCornerShape(16.dp))
233 | .background(
234 | brush = Brush.verticalGradient(
235 | colors = listOf(
236 | Color.Transparent,
237 | Color.Black.copy(alpha = 0.7f)
238 | ),
239 | startY = 100f
240 | )
241 | )
242 | ) {
243 | KamelImage(
244 | { image }, contentDescription = null,
245 | modifier = Modifier.matchParentSize(),
246 | contentScale = ContentScale.Crop
247 | )
248 | Text(
249 | text = news.title,
250 | style = MaterialTheme.typography.headlineSmall.copy(color = Color.White),
251 | modifier = Modifier
252 | .align(Alignment.BottomStart)
253 | .padding(8.dp)
254 | )
255 | }
256 | Spacer(modifier = Modifier.height(8.dp))
257 | Text(
258 | text = news.body,
259 | style = MaterialTheme.typography.bodyMedium,
260 | maxLines = 3,
261 | overflow = TextOverflow.Ellipsis,
262 | color = MaterialTheme.colorScheme.onSurface
263 | )
264 | Spacer(modifier = Modifier.height(8.dp))
265 | Row(
266 | verticalAlignment = Alignment.CenterVertically,
267 | horizontalArrangement = Arrangement.SpaceBetween
268 | ) {
269 | Row(verticalAlignment = Alignment.CenterVertically) {
270 | AsyncImage(
271 | model = news.sourceInfo.img,
272 | contentDescription = null,
273 | modifier = Modifier
274 | .size(24.dp)
275 | .clip(CircleShape)
276 | )
277 | Spacer(modifier = Modifier.width(8.dp))
278 | Text(
279 | text = news.sourceInfo.name,
280 | style = MaterialTheme.typography.labelMedium,
281 | color = MaterialTheme.colorScheme.primary
282 | )
283 | }
284 | Text(
285 | text = " Published: ${formatTimestamp(news.publishedOn.toLong())}",
286 | style = MaterialTheme.typography.bodySmall,
287 | color = MaterialTheme.colorScheme.onSurfaceVariant
288 | )
289 | }
290 | }
291 | }
292 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/ui/screens/profile/ProfileScreen.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.ui.screens.profile
2 |
3 | import androidx.compose.material3.Text
4 | import androidx.compose.runtime.Composable
5 | import cafe.adriel.voyager.core.screen.Screen
6 |
7 | class ProfileScreen : Screen {
8 | @Composable
9 | override fun Content() {
10 | ProfileContent()
11 | }
12 | }
13 |
14 | @Composable
15 | fun ProfileContent() {
16 | Text("Profile Content")
17 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/presentation/viewmodel/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.presentation.viewmodel
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import kotlinx.coroutines.flow.MutableStateFlow
6 | import kotlinx.coroutines.flow.StateFlow
7 | import kotlinx.coroutines.flow.asStateFlow
8 | import kotlinx.coroutines.launch
9 | import org.company.app.domain.model.categories.NewsCategoriesItem
10 | import org.company.app.domain.model.crypto.LatestListing
11 | import org.company.app.domain.model.news.NewsList
12 | import org.company.app.domain.repository.Repository
13 | import org.company.app.domain.usecase.ResultState
14 |
15 | class MainViewModel(private val repository: Repository) : ViewModel() {
16 | private val _latestListing = MutableStateFlow>(ResultState.LOADING)
17 | var latestListing: StateFlow> = _latestListing.asStateFlow()
18 |
19 | private val _allNews = MutableStateFlow>(ResultState.LOADING)
20 | var allNews: StateFlow> = _allNews.asStateFlow()
21 |
22 | private val _newsCategories = MutableStateFlow>>(ResultState.LOADING)
23 | var newsCategories: StateFlow>> = _newsCategories.asStateFlow()
24 |
25 | fun getLatestListing() {
26 | viewModelScope.launch {
27 | _latestListing.value = ResultState.LOADING
28 | try {
29 | val response = repository.getLatestListing()
30 | _latestListing.value = ResultState.SUCCESS(response)
31 | } catch (e: Exception) {
32 | _latestListing.value = ResultState.ERROR(e.message.toString())
33 | }
34 | }
35 | }
36 |
37 | fun getAllNews(){
38 | viewModelScope.launch {
39 | _allNews.value = ResultState.LOADING
40 | try {
41 | val response = repository.getAllNews()
42 | _allNews.value = ResultState.SUCCESS(response)
43 | }catch (e:Exception){
44 | _allNews.value = ResultState.ERROR(e.message.toString())
45 | }
46 | }
47 | }
48 | fun getNewsCategories(){
49 | viewModelScope.launch {
50 | _newsCategories.value = ResultState.LOADING
51 | try {
52 | val response = repository.getNewsCategories()
53 | _newsCategories.value = ResultState.SUCCESS(response)
54 | }catch (e:Exception){
55 | _newsCategories.value = ResultState.ERROR(e.message.toString())
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | //generated by https://m3.material.io/theme-builder#/custom
6 | //Color palette was taken here: https://colorhunt.co/palettes/popular
7 |
8 | internal val md_theme_light_primary = Color(0xFF00687A)
9 | internal val md_theme_light_onPrimary = Color(0xFFFFFFFF)
10 | internal val md_theme_light_primaryContainer = Color(0xFFABEDFF)
11 | internal val md_theme_light_onPrimaryContainer = Color(0xFF001F26)
12 | internal val md_theme_light_secondary = Color(0xFF00696E)
13 | internal val md_theme_light_onSecondary = Color(0xFFFFFFFF)
14 | internal val md_theme_light_secondaryContainer = Color(0xFF6FF6FE)
15 | internal val md_theme_light_onSecondaryContainer = Color(0xFF002022)
16 | internal val md_theme_light_tertiary = Color(0xFF904D00)
17 | internal val md_theme_light_onTertiary = Color(0xFFFFFFFF)
18 | internal val md_theme_light_tertiaryContainer = Color(0xFFFFDCC2)
19 | internal val md_theme_light_onTertiaryContainer = Color(0xFF2E1500)
20 | internal val md_theme_light_error = Color(0xFFBA1A1A)
21 | internal val md_theme_light_errorContainer = Color(0xFFFFDAD6)
22 | internal val md_theme_light_onError = Color(0xFFFFFFFF)
23 | internal val md_theme_light_onErrorContainer = Color(0xFF410002)
24 | internal val md_theme_light_background = Color(0xFFFFFBFF)
25 | internal val md_theme_light_onBackground = Color(0xFF221B00)
26 | internal val md_theme_light_surface = Color(0xFFFFFBFF)
27 | internal val md_theme_light_onSurface = Color(0xFF221B00)
28 | internal val md_theme_light_surfaceVariant = Color(0xFFDBE4E7)
29 | internal val md_theme_light_onSurfaceVariant = Color(0xFF3F484B)
30 | internal val md_theme_light_outline = Color(0xFF70797B)
31 | internal val md_theme_light_inverseOnSurface = Color(0xFFFFF0C0)
32 | internal val md_theme_light_inverseSurface = Color(0xFF3A3000)
33 | internal val md_theme_light_inversePrimary = Color(0xFF55D6F4)
34 | internal val md_theme_light_shadow = Color(0xFF000000)
35 | internal val md_theme_light_surfaceTint = Color(0xFF00687A)
36 | internal val md_theme_light_outlineVariant = Color(0xFFBFC8CB)
37 | internal val md_theme_light_scrim = Color(0xFF000000)
38 |
39 | internal val md_theme_dark_primary = Color(0xFF55D6F4)
40 | internal val md_theme_dark_onPrimary = Color(0xFF003640)
41 | internal val md_theme_dark_primaryContainer = Color(0xFF004E5C)
42 | internal val md_theme_dark_onPrimaryContainer = Color(0xFFABEDFF)
43 | internal val md_theme_dark_secondary = Color(0xFF4CD9E2)
44 | internal val md_theme_dark_onSecondary = Color(0xFF00373A)
45 | internal val md_theme_dark_secondaryContainer = Color(0xFF004F53)
46 | internal val md_theme_dark_onSecondaryContainer = Color(0xFF6FF6FE)
47 | internal val md_theme_dark_tertiary = Color(0xFFFFB77C)
48 | internal val md_theme_dark_onTertiary = Color(0xFF4D2700)
49 | internal val md_theme_dark_tertiaryContainer = Color(0xFF6D3900)
50 | internal val md_theme_dark_onTertiaryContainer = Color(0xFFFFDCC2)
51 | internal val md_theme_dark_error = Color(0xFFFFB4AB)
52 | internal val md_theme_dark_errorContainer = Color(0xFF93000A)
53 | internal val md_theme_dark_onError = Color(0xFF690005)
54 | internal val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
55 | internal val md_theme_dark_background = Color(0xFF221B00)
56 | internal val md_theme_dark_onBackground = Color(0xFFFFE264)
57 | internal val md_theme_dark_surface = Color(0xFF221B00)
58 | internal val md_theme_dark_onSurface = Color(0xFFFFE264)
59 | internal val md_theme_dark_surfaceVariant = Color(0xFF3F484B)
60 | internal val md_theme_dark_onSurfaceVariant = Color(0xFFBFC8CB)
61 | internal val md_theme_dark_outline = Color(0xFF899295)
62 | internal val md_theme_dark_inverseOnSurface = Color(0xFF221B00)
63 | internal val md_theme_dark_inverseSurface = Color(0xFFFFE264)
64 | internal val md_theme_dark_inversePrimary = Color(0xFF00687A)
65 | internal val md_theme_dark_shadow = Color(0xFF000000)
66 | internal val md_theme_dark_surfaceTint = Color(0xFF55D6F4)
67 | internal val md_theme_dark_outlineVariant = Color(0xFF3F484B)
68 | internal val md_theme_dark_scrim = Color(0xFF000000)
69 |
70 |
71 | internal val seed = Color(0xFF2C3639)
72 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.Surface
6 | import androidx.compose.material3.darkColorScheme
7 | import androidx.compose.material3.lightColorScheme
8 | import androidx.compose.runtime.*
9 |
10 | private val LightColorScheme = lightColorScheme(
11 | primary = md_theme_light_primary,
12 | onPrimary = md_theme_light_onPrimary,
13 | primaryContainer = md_theme_light_primaryContainer,
14 | onPrimaryContainer = md_theme_light_onPrimaryContainer,
15 | secondary = md_theme_light_secondary,
16 | onSecondary = md_theme_light_onSecondary,
17 | secondaryContainer = md_theme_light_secondaryContainer,
18 | onSecondaryContainer = md_theme_light_onSecondaryContainer,
19 | tertiary = md_theme_light_tertiary,
20 | onTertiary = md_theme_light_onTertiary,
21 | tertiaryContainer = md_theme_light_tertiaryContainer,
22 | onTertiaryContainer = md_theme_light_onTertiaryContainer,
23 | error = md_theme_light_error,
24 | errorContainer = md_theme_light_errorContainer,
25 | onError = md_theme_light_onError,
26 | onErrorContainer = md_theme_light_onErrorContainer,
27 | background = md_theme_light_background,
28 | onBackground = md_theme_light_onBackground,
29 | surface = md_theme_light_surface,
30 | onSurface = md_theme_light_onSurface,
31 | surfaceVariant = md_theme_light_surfaceVariant,
32 | onSurfaceVariant = md_theme_light_onSurfaceVariant,
33 | outline = md_theme_light_outline,
34 | inverseOnSurface = md_theme_light_inverseOnSurface,
35 | inverseSurface = md_theme_light_inverseSurface,
36 | inversePrimary = md_theme_light_inversePrimary,
37 | surfaceTint = md_theme_light_surfaceTint,
38 | outlineVariant = md_theme_light_outlineVariant,
39 | scrim = md_theme_light_scrim,
40 | )
41 |
42 | private val DarkColorScheme = darkColorScheme(
43 | primary = md_theme_dark_primary,
44 | onPrimary = md_theme_dark_onPrimary,
45 | primaryContainer = md_theme_dark_primaryContainer,
46 | onPrimaryContainer = md_theme_dark_onPrimaryContainer,
47 | secondary = md_theme_dark_secondary,
48 | onSecondary = md_theme_dark_onSecondary,
49 | secondaryContainer = md_theme_dark_secondaryContainer,
50 | onSecondaryContainer = md_theme_dark_onSecondaryContainer,
51 | tertiary = md_theme_dark_tertiary,
52 | onTertiary = md_theme_dark_onTertiary,
53 | tertiaryContainer = md_theme_dark_tertiaryContainer,
54 | onTertiaryContainer = md_theme_dark_onTertiaryContainer,
55 | error = md_theme_dark_error,
56 | errorContainer = md_theme_dark_errorContainer,
57 | onError = md_theme_dark_onError,
58 | onErrorContainer = md_theme_dark_onErrorContainer,
59 | background = md_theme_dark_background,
60 | onBackground = md_theme_dark_onBackground,
61 | surface = md_theme_dark_surface,
62 | onSurface = md_theme_dark_onSurface,
63 | surfaceVariant = md_theme_dark_surfaceVariant,
64 | onSurfaceVariant = md_theme_dark_onSurfaceVariant,
65 | outline = md_theme_dark_outline,
66 | inverseOnSurface = md_theme_dark_inverseOnSurface,
67 | inverseSurface = md_theme_dark_inverseSurface,
68 | inversePrimary = md_theme_dark_inversePrimary,
69 | surfaceTint = md_theme_dark_surfaceTint,
70 | outlineVariant = md_theme_dark_outlineVariant,
71 | scrim = md_theme_dark_scrim,
72 | )
73 |
74 | internal val LocalThemeIsDark = compositionLocalOf { mutableStateOf(true) }
75 |
76 | @Composable
77 | internal fun AppTheme(
78 | content: @Composable() () -> Unit
79 | ) {
80 | val systemIsDark = isSystemInDarkTheme()
81 | val isDarkState = remember { mutableStateOf(systemIsDark) }
82 | CompositionLocalProvider(
83 | LocalThemeIsDark provides isDarkState
84 | ) {
85 | val isDark by isDarkState
86 | SystemAppearance(!isDark)
87 | MaterialTheme(
88 | colorScheme = if (isDark) DarkColorScheme else LightColorScheme,
89 | content = { Surface(content = content) }
90 | )
91 | }
92 | }
93 |
94 | @Composable
95 | internal expect fun SystemAppearance(isDark: Boolean)
96 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/utils/Constant.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.utils
2 |
3 | object Constant {
4 | const val BASE_URL = "https://pro-api.coinmarketcap.com/v1/"
5 | const val CRYPTO_URL = "https://min-api.cryptocompare.com/data/"
6 | const val API_KEY = "55d74250-258c-44bb-ba06-57a19719ffa0"
7 | const val TIME_OUT: Long = 150000
8 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/utils/formatMarketCap.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.utils
2 |
3 | import com.ionspin.kotlin.bignum.decimal.toBigDecimal
4 |
5 | fun formatMarketCap(marketCap: Double): String {
6 | val suffixes = listOf("", "K", "M", "B", "T")
7 | var value = marketCap.toBigDecimal()
8 | var index = 0
9 |
10 | while (value >= 1000.toBigDecimal() && index < suffixes.size - 1) {
11 | value /= 1000.toBigDecimal()
12 | index++
13 | }
14 | return "${value.toPlainString().take(5)}${suffixes[index]}"
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/org/company/app/utils/formateTimeStamp.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.utils
2 |
3 | import kotlinx.datetime.Instant
4 | import kotlinx.datetime.TimeZone
5 | import kotlinx.datetime.toLocalDateTime
6 |
7 | fun formatTimestamp(timestamp: Long): String {
8 | val instant = Instant.fromEpochSeconds(timestamp)
9 | val localDateTime = instant.toLocalDateTime(TimeZone.currentSystemDefault())
10 |
11 | val day = localDateTime.date.dayOfMonth.toString().padStart(2, '0')
12 | val month = localDateTime.date.month.name.lowercase().replaceFirstChar { it.uppercase() }
13 | val year = localDateTime.date.year.toString()
14 |
15 | return "$day $month $year"
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/composeApp/src/commonTest/kotlin/org/company/app/ComposeTest.kt:
--------------------------------------------------------------------------------
1 | package org.company.app
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material3.Button
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.runtime.setValue
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.testTag
12 | import androidx.compose.ui.test.ExperimentalTestApi
13 | import androidx.compose.ui.test.assertTextEquals
14 | import androidx.compose.ui.test.onNodeWithTag
15 | import androidx.compose.ui.test.performClick
16 | import androidx.compose.ui.test.runComposeUiTest
17 | import kotlin.test.Test
18 |
19 | @OptIn(ExperimentalTestApi::class)
20 | class ComposeTest {
21 |
22 | @Test
23 | fun simpleCheck() = runComposeUiTest {
24 | setContent {
25 | var txt by remember { mutableStateOf("Go") }
26 | Column {
27 | Text(
28 | text = txt,
29 | modifier = Modifier.testTag("t_text")
30 | )
31 | Button(
32 | onClick = { txt += "." },
33 | modifier = Modifier.testTag("t_button")
34 | ) {
35 | Text("click me")
36 | }
37 | }
38 | }
39 |
40 | onNodeWithTag("t_button").apply {
41 | repeat(3) { performClick() }
42 | }
43 | onNodeWithTag("t_text").assertTextEquals("Go...")
44 | }
45 | }
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.ComposeUIViewController
2 | import org.company.app.App
3 | import platform.UIKit.UIViewController
4 |
5 | fun MainViewController(): UIViewController = ComposeUIViewController(configure = { enforceStrictPlistSanityCheck = false }) { App() }
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/org/company/app/App.ios.kt:
--------------------------------------------------------------------------------
1 | package org.company.app
2 |
3 | import platform.Foundation.NSURL
4 | import platform.UIKit.UIApplication
5 |
6 | internal actual fun openUrl(url: String?) {
7 | val nsUrl = url?.let { NSURL.URLWithString(it) } ?: return
8 | UIApplication.sharedApplication.openURL(nsUrl)
9 | }
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/org/company/app/theme/Theme.ios.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.theme
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import platform.UIKit.UIApplication
6 | import platform.UIKit.UIStatusBarStyleDarkContent
7 | import platform.UIKit.UIStatusBarStyleLightContent
8 | import platform.UIKit.setStatusBarStyle
9 |
10 | @Composable
11 | internal actual fun SystemAppearance(isDark: Boolean) {
12 | LaunchedEffect(isDark) {
13 | UIApplication.sharedApplication.setStatusBarStyle(
14 | if (isDark) UIStatusBarStyleDarkContent else UIStatusBarStyleLightContent
15 | )
16 | }
17 | }
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.ExperimentalComposeUiApi
2 | import androidx.compose.ui.window.CanvasBasedWindow
3 | import org.company.app.App
4 | import org.company.app.di.appModule
5 | import org.jetbrains.skiko.wasm.onWasmReady
6 | import org.koin.core.context.startKoin
7 |
8 | @OptIn(ExperimentalComposeUiApi::class)
9 | fun main() {
10 | onWasmReady {
11 | CanvasBasedWindow("Crypto-KMP") {
12 | App()
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/org/company/app/App.js.kt:
--------------------------------------------------------------------------------
1 | package org.company.app
2 |
3 | import kotlinx.browser.window
4 |
5 | internal actual fun openUrl(url: String?) {
6 | url?.let { window.open(it) }
7 | }
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/org/company/app/theme/Theme.js.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.theme
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | internal actual fun SystemAppearance(isDark: Boolean) {
7 | }
--------------------------------------------------------------------------------
/composeApp/src/jsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Crypto-KMP
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.unit.dp
2 | import androidx.compose.ui.window.Window
3 | import androidx.compose.ui.window.application
4 | import androidx.compose.ui.window.rememberWindowState
5 | import org.company.app.App
6 | import java.awt.Dimension
7 |
8 | fun main() = application {
9 | Window(
10 | title = "Crypto-KMP",
11 | state = rememberWindowState(width = 1280.dp, height = 720.dp),
12 | onCloseRequest = ::exitApplication,
13 | ) {
14 | window.minimumSize = Dimension(800, 600)
15 | App()
16 | }
17 | }
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/org/company/app/App.jvm.kt:
--------------------------------------------------------------------------------
1 | package org.company.app
2 |
3 | import java.awt.Desktop
4 | import java.net.URI
5 |
6 | internal actual fun openUrl(url: String?) {
7 | val uri = url?.let { URI.create(it) } ?: return
8 | Desktop.getDesktop().browse(uri)
9 | }
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/org/company/app/theme/Theme.jvm.kt:
--------------------------------------------------------------------------------
1 | package org.company.app.theme
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | internal actual fun SystemAppearance(isDark: Boolean) {
7 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #Gradle
2 | org.gradle.jvmargs=-Xmx4G -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx4G"
3 | org.gradle.caching=true
4 | org.gradle.configuration-cache=true
5 | org.gradle.daemon=true
6 | org.gradle.parallel=true
7 |
8 | #Kotlin
9 | kotlin.code.style=official
10 | kotlin.js.compiler=ir
11 |
12 | #Android
13 | android.useAndroidX=true
14 | android.nonTransitiveRClass=true
15 |
16 | #Compose
17 | org.jetbrains.compose.experimental.uikit.enabled=true
18 | org.jetbrains.compose.experimental.jscanvas.enabled=true
19 | org.jetbrains.compose.experimental.wasm.enabled=true
20 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 |
3 | alertKmp = "2.0.0"
4 | bignum = "0.3.9"
5 | chart = "Beta-0.0.5"
6 | kamelImage = "1.0.1"
7 | koinAnnotations = "1.3.1"
8 | kotlin = "2.0.21"
9 | compose = "1.7.1"
10 | agp = "8.2.2"
11 | androidx-activityCompose = "1.9.3"
12 | androidx-uiTest = "1.7.6"
13 | lifecycleViewmodelCompose = "2.8.4"
14 | voyager = "1.1.0-beta03"
15 | napier = "2.7.1"
16 | buildConfig = "5.3.5"
17 | kotlinx-coroutines = "1.9.0"
18 | ktor = "3.0.1"
19 | kotlinx-serialization = "1.7.3"
20 | kotlinx-datetime = "0.6.1"
21 | sqlDelight = "2.0.2"
22 | size = "0.5.0"
23 | coil3 = "3.0.0-alpha08"
24 |
25 |
26 | [libraries]
27 |
28 | alert-kmp = { module = "io.github.khubaibkhan4:alert-kmp", version.ref = "alertKmp" }
29 | androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
30 | androidx-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-uiTest" }
31 | androidx-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-uiTest" }
32 | bignum = { module = "com.ionspin.kotlin:bignum", version.ref = "bignum" }
33 | chart = { module = "io.github.thechance101:chart", version.ref = "chart" }
34 | insert-koin-koin-core = { module = "io.insert-koin:koin-core" }
35 | io-insert-koin-koin-core = { module = "io.insert-koin:koin-core" }
36 | kamel-image = { module = "media.kamel:kamel-image", version.ref = "kamelImage" }
37 | kamel-image-default = { module = "media.kamel:kamel-image-default", version.ref = "kamelImage" }
38 | koin-android = { module = "io.insert-koin:koin-android" }
39 | koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koinAnnotations" }
40 | koin-compose = { module = "io.insert-koin:koin-compose" }
41 | lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
42 | voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
43 | tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
44 | napier = { module = "io.github.aakira:napier", version.ref = "napier" }
45 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
46 | kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
47 | kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
48 | kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
49 | ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
50 | ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
51 | ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
52 | ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
53 | ktor-client-content-negociation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
54 | ktor-client-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
55 | ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
56 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
57 | kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
58 | screen-size = { module = "dev.chrisbanes.material3:material3-window-size-class-multiplatform", version.ref = "size" }
59 | sqlDelight-driver-sqlite = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqlDelight" }
60 | sqlDelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" }
61 | sqlDelight-driver-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" }
62 | sqlDelight-driver-js = { module = "app.cash.sqldelight:web-worker-driver", version.ref = "sqlDelight" }
63 | coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" }
64 | coil-compose-core = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "coil3" }
65 | coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor", version.ref = "coil3" }
66 | coil-mp = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" }
67 |
68 | [plugins]
69 |
70 | multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
71 | compose = { id = "org.jetbrains.compose", version.ref = "compose" }
72 | android-application = { id = "com.android.application", version.ref = "agp" }
73 | buildConfig = { id = "com.github.gmazzo.buildconfig", version.ref = "buildConfig" }
74 | kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
75 | sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" }
76 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
77 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhubaibKhan4/Crypto-KMP/1298614b35728f6345fa0743e181f0979f0b6fd8/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 |
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 |
2 | #!/bin/sh
3 |
4 | #
5 | # Copyright © 2015-2021 the original authors.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | ##############################################################################
21 | #
22 | # Gradle start up script for POSIX generated by Gradle.
23 | #
24 | # Important for running:
25 | #
26 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
27 | # noncompliant, but you have some other compliant shell such as ksh or
28 | # bash, then to run this script, type that shell name before the whole
29 | # command line, like:
30 | #
31 | # ksh Gradle
32 | #
33 | # Busybox and similar reduced shells will NOT work, because this script
34 | # requires all of these POSIX shell features:
35 | # * functions;
36 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
37 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
38 | # * compound commands having a testable exit status, especially «case»;
39 | # * various built-in commands including «command», «set», and «ulimit».
40 | #
41 | # Important for patching:
42 | #
43 | # (2) This script targets any POSIX shell, so it avoids extensions provided
44 | # by Bash, Ksh, etc; in particular arrays are avoided.
45 | #
46 | # The "traditional" practice of packing multiple parameters into a
47 | # space-separated string is a well documented source of bugs and security
48 | # problems, so this is (mostly) avoided, by progressively accumulating
49 | # options in "$@", and eventually passing that to Java.
50 | #
51 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
52 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
53 | # see the in-line comments for details.
54 | #
55 | # There are tweaks for specific operating systems such as AIX, CygWin,
56 | # Darwin, MinGW, and NonStop.
57 | #
58 | # (3) This script is generated from the Groovy template
59 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
60 | # within the Gradle project.
61 | #
62 | # You can find Gradle at https://github.com/gradle/gradle/.
63 | #
64 | ##############################################################################
65 |
66 | # Attempt to set APP_HOME
67 |
68 | # Resolve links: $0 may be a link
69 | app_path=$0
70 |
71 | # Need this for daisy-chained symlinks.
72 | while
73 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
74 | [ -h "$app_path" ]
75 | do
76 | ls=$( ls -ld "$app_path" )
77 | link=${ls#*' -> '}
78 | case $link in #(
79 | /*) app_path=$link ;; #(
80 | *) app_path=$APP_HOME$link ;;
81 | esac
82 | done
83 |
84 | # This is normally unused
85 | # shellcheck disable=SC2034
86 | APP_BASE_NAME=${0##*/}
87 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
88 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
89 |
90 | # Use the maximum available, or set MAX_FD != -1 to use that value.
91 | MAX_FD=maximum
92 |
93 | warn () {
94 | echo "$*"
95 | } >&2
96 |
97 | die () {
98 | echo
99 | echo "$*"
100 | echo
101 | exit 1
102 | } >&2
103 |
104 | # OS specific support (must be 'true' or 'false').
105 | cygwin=false
106 | msys=false
107 | darwin=false
108 | nonstop=false
109 | case "$( uname )" in #(
110 | CYGWIN* ) cygwin=true ;; #(
111 | Darwin* ) darwin=true ;; #(
112 | MSYS* | MINGW* ) msys=true ;; #(
113 | NONSTOP* ) nonstop=true ;;
114 | esac
115 |
116 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
117 |
118 |
119 | # Determine the Java command to use to start the JVM.
120 | if [ -n "$JAVA_HOME" ] ; then
121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
122 | # IBM's JDK on AIX uses strange locations for the executables
123 | JAVACMD=$JAVA_HOME/jre/sh/java
124 | else
125 | JAVACMD=$JAVA_HOME/bin/java
126 | fi
127 | if [ ! -x "$JAVACMD" ] ; then
128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
129 |
130 | Please set the JAVA_HOME variable in your environment to match the
131 | location of your Java installation."
132 | fi
133 | else
134 | JAVACMD=java
135 | if ! command -v java >/dev/null 2>&1
136 | then
137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
138 |
139 | Please set the JAVA_HOME variable in your environment to match the
140 | location of your Java installation."
141 | fi
142 | fi
143 |
144 | # Increase the maximum file descriptors if we can.
145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
146 | case $MAX_FD in #(
147 | max*)
148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
149 | # shellcheck disable=SC2039,SC3045
150 | MAX_FD=$( ulimit -H -n ) ||
151 | warn "Could not query maximum file descriptor limit"
152 | esac
153 | case $MAX_FD in #(
154 | '' | soft) :;; #(
155 | *)
156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
157 | # shellcheck disable=SC2039,SC3045
158 | ulimit -n "$MAX_FD" ||
159 | warn "Could not set maximum file descriptor limit to $MAX_FD"
160 | esac
161 | fi
162 |
163 | # Collect all arguments for the java command, stacking in reverse order:
164 | # * args from the command line
165 | # * the main class name
166 | # * -classpath
167 | # * -D...appname settings
168 | # * --module-path (only if needed)
169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
170 |
171 | # For Cygwin or MSYS, switch paths to Windows format before running java
172 | if "$cygwin" || "$msys" ; then
173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
174 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
175 |
176 | JAVACMD=$( cygpath --unix "$JAVACMD" )
177 |
178 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
179 | for arg do
180 | if
181 | case $arg in #(
182 | -*) false ;; # don't mess with options #(
183 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
184 | [ -e "$t" ] ;; #(
185 | *) false ;;
186 | esac
187 | then
188 | arg=$( cygpath --path --ignore --mixed "$arg" )
189 | fi
190 | # Roll the args list around exactly as many times as the number of
191 | # args, so each arg winds up back in the position where it started, but
192 | # possibly modified.
193 | #
194 | # NB: a `for` loop captures its iteration list before it begins, so
195 | # changing the positional parameters here affects neither the number of
196 | # iterations, nor the values presented in `arg`.
197 | shift # remove old arg
198 | set -- "$@" "$arg" # push replacement arg
199 | done
200 | fi
201 |
202 |
203 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
204 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
205 |
206 | # Collect all arguments for the java command:
207 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
208 | # and any embedded shellness will be escaped.
209 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
210 | # treated as '${Hostname}' itself on the command line.
211 |
212 | set -- \
213 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
214 | -classpath "$CLASSPATH" \
215 | org.gradle.wrapper.GradleWrapperMain \
216 | "$@"
217 |
218 | # Stop when "xargs" is not available.
219 | if ! command -v xargs >/dev/null 2>&1
220 | then
221 | die "xargs is not available"
222 | fi
223 |
224 | # Use "xargs" to parse quoted args.
225 | #
226 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
227 | #
228 | # In Bash we could simply go:
229 | #
230 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
231 | # set -- "${ARGS[@]}" "$@"
232 | #
233 | # but POSIX shell has neither arrays nor command substitution, so instead we
234 | # post-process each arg (as a line of input to sed) to backslash-escape any
235 | # character that might be a shell metacharacter, then use eval to reverse
236 | # that process (while maintaining the separation between arguments), and wrap
237 | # the whole thing up as a single "set" statement.
238 | #
239 | # This will of course break if any of these variables contains a newline or
240 | # an unmatched quote.
241 | #
242 |
243 | eval "set -- $(
244 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
245 | xargs -n1 |
246 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
247 | tr '\n' ' '
248 | )" '"$@"'
249 |
250 | exec "$JAVACMD" "$@"
251 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 |
2 | @rem
3 | @rem Copyright 2015 the original author or authors.
4 | @rem
5 | @rem Licensed under the Apache License, Version 2.0 (the "License");
6 | @rem you may not use this file except in compliance with the License.
7 | @rem You may obtain a copy of the License at
8 | @rem
9 | @rem https://www.apache.org/licenses/LICENSE-2.0
10 | @rem
11 | @rem Unless required by applicable law or agreed to in writing, software
12 | @rem distributed under the License is distributed on an "AS IS" BASIS,
13 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | @rem See the License for the specific language governing permissions and
15 | @rem limitations under the License.
16 | @rem
17 |
18 | @if "%DEBUG%"=="" @echo off
19 | @rem ##########################################################################
20 | @rem
21 | @rem Gradle startup script for Windows
22 | @rem
23 | @rem ##########################################################################
24 |
25 | @rem Set local scope for the variables with windows NT shell
26 | if "%OS%"=="Windows_NT" setlocal
27 |
28 | set DIRNAME=%~dp0
29 | if "%DIRNAME%"=="" set DIRNAME=.
30 | @rem This is normally unused
31 | set APP_BASE_NAME=%~n0
32 | set APP_HOME=%DIRNAME%
33 |
34 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
35 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
36 |
37 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
38 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
39 |
40 | @rem Find java.exe
41 | if defined JAVA_HOME goto findJavaFromJavaHome
42 |
43 | set JAVA_EXE=java.exe
44 | %JAVA_EXE% -version >NUL 2>&1
45 | if %ERRORLEVEL% equ 0 goto execute
46 |
47 | echo. 1>&2
48 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
49 | echo. 1>&2
50 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
51 | echo location of your Java installation. 1>&2
52 |
53 | goto fail
54 |
55 | :findJavaFromJavaHome
56 | set JAVA_HOME=%JAVA_HOME:"=%
57 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
58 |
59 | if exist "%JAVA_EXE%" goto execute
60 |
61 | echo. 1>&2
62 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
63 | echo. 1>&2
64 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
65 | echo location of your Java installation. 1>&2
66 |
67 | goto fail
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 |
75 | @rem Execute Gradle
76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
77 |
78 | :end
79 | @rem End local scope for the variables with windows NT shell
80 | if %ERRORLEVEL% equ 0 goto mainEnd
81 |
82 | :fail
83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
84 | rem the _cmd.exe /c_ return code!
85 | set EXIT_CODE=%ERRORLEVEL%
86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
88 | exit /b %EXIT_CODE%
89 |
90 | :mainEnd
91 | if "%OS%"=="Windows_NT" endlocal
92 |
93 | :omega
94 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | A93A953B29CC810C00F8E227 /* iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93A953A29CC810C00F8E227 /* iosApp.swift */; };
11 | A93A953F29CC810D00F8E227 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A93A953E29CC810D00F8E227 /* Assets.xcassets */; };
12 | A93A954229CC810D00F8E227 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A93A954129CC810D00F8E227 /* Preview Assets.xcassets */; };
13 | /* End PBXBuildFile section */
14 |
15 | /* Begin PBXFileReference section */
16 | A93A953729CC810C00F8E227 /* Crypto-KMP.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Crypto-KMP.app"; sourceTree = BUILT_PRODUCTS_DIR; };
17 | A93A953A29CC810C00F8E227 /* iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosApp.swift; sourceTree = ""; };
18 | A93A953E29CC810D00F8E227 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
19 | A93A954129CC810D00F8E227 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
20 | /* End PBXFileReference section */
21 |
22 | /* Begin PBXFrameworksBuildPhase section */
23 | A93A953429CC810C00F8E227 /* Frameworks */ = {
24 | isa = PBXFrameworksBuildPhase;
25 | buildActionMask = 2147483647;
26 | files = (
27 | );
28 | runOnlyForDeploymentPostprocessing = 0;
29 | };
30 | /* End PBXFrameworksBuildPhase section */
31 |
32 | /* Begin PBXGroup section */
33 | A93A952E29CC810C00F8E227 = {
34 | isa = PBXGroup;
35 | children = (
36 | A93A953929CC810C00F8E227 /* iosApp */,
37 | A93A953829CC810C00F8E227 /* Products */,
38 | C4127409AE3703430489E7BC /* Frameworks */,
39 | );
40 | sourceTree = "";
41 | };
42 | A93A953829CC810C00F8E227 /* Products */ = {
43 | isa = PBXGroup;
44 | children = (
45 | A93A953729CC810C00F8E227 /* Crypto-KMP.app */,
46 | );
47 | name = Products;
48 | sourceTree = "";
49 | };
50 | A93A953929CC810C00F8E227 /* iosApp */ = {
51 | isa = PBXGroup;
52 | children = (
53 | A93A953A29CC810C00F8E227 /* iosApp.swift */,
54 | A93A953E29CC810D00F8E227 /* Assets.xcassets */,
55 | A93A954029CC810D00F8E227 /* Preview Content */,
56 | );
57 | path = iosApp;
58 | sourceTree = "";
59 | };
60 | A93A954029CC810D00F8E227 /* Preview Content */ = {
61 | isa = PBXGroup;
62 | children = (
63 | A93A954129CC810D00F8E227 /* Preview Assets.xcassets */,
64 | );
65 | path = "Preview Content";
66 | sourceTree = "";
67 | };
68 | C4127409AE3703430489E7BC /* Frameworks */ = {
69 | isa = PBXGroup;
70 | children = (
71 | );
72 | name = Frameworks;
73 | sourceTree = "";
74 | };
75 | /* End PBXGroup section */
76 |
77 | /* Begin PBXNativeTarget section */
78 | A93A953629CC810C00F8E227 /* iosApp */ = {
79 | isa = PBXNativeTarget;
80 | buildConfigurationList = A93A954529CC810D00F8E227 /* Build configuration list for PBXNativeTarget "iosApp" */;
81 | buildPhases = (
82 | A9D80A052AAB5CDE006C8738 /* ShellScript */,
83 | A93A953329CC810C00F8E227 /* Sources */,
84 | A93A953429CC810C00F8E227 /* Frameworks */,
85 | A93A953529CC810C00F8E227 /* Resources */,
86 | );
87 | buildRules = (
88 | );
89 | dependencies = (
90 | );
91 | name = iosApp;
92 | productName = iosApp;
93 | productReference = A93A953729CC810C00F8E227 /* Crypto-KMP.app */;
94 | productType = "com.apple.product-type.application";
95 | };
96 | /* End PBXNativeTarget section */
97 |
98 | /* Begin PBXProject section */
99 | A93A952F29CC810C00F8E227 /* Project object */ = {
100 | isa = PBXProject;
101 | attributes = {
102 | LastSwiftUpdateCheck = 1420;
103 | LastUpgradeCheck = 1420;
104 | TargetAttributes = {
105 | A93A953629CC810C00F8E227 = {
106 | CreatedOnToolsVersion = 14.2;
107 | };
108 | };
109 | };
110 | buildConfigurationList = A93A953229CC810C00F8E227 /* Build configuration list for PBXProject "iosApp" */;
111 | compatibilityVersion = "Xcode 14.0";
112 | developmentRegion = en;
113 | hasScannedForEncodings = 0;
114 | knownRegions = (
115 | en,
116 | Base,
117 | );
118 | mainGroup = A93A952E29CC810C00F8E227;
119 | productRefGroup = A93A953829CC810C00F8E227 /* Products */;
120 | projectDirPath = "";
121 | projectRoot = "";
122 | targets = (
123 | A93A953629CC810C00F8E227 /* iosApp */,
124 | );
125 | };
126 | /* End PBXProject section */
127 |
128 | /* Begin PBXResourcesBuildPhase section */
129 | A93A953529CC810C00F8E227 /* Resources */ = {
130 | isa = PBXResourcesBuildPhase;
131 | buildActionMask = 2147483647;
132 | files = (
133 | A93A954229CC810D00F8E227 /* Preview Assets.xcassets in Resources */,
134 | A93A953F29CC810D00F8E227 /* Assets.xcassets in Resources */,
135 | );
136 | runOnlyForDeploymentPostprocessing = 0;
137 | };
138 | /* End PBXResourcesBuildPhase section */
139 |
140 | /* Begin PBXShellScriptBuildPhase section */
141 | A9D80A052AAB5CDE006C8738 /* ShellScript */ = {
142 | isa = PBXShellScriptBuildPhase;
143 | buildActionMask = 2147483647;
144 | files = (
145 | );
146 | inputFileListPaths = (
147 | );
148 | inputPaths = (
149 | );
150 | outputFileListPaths = (
151 | );
152 | outputPaths = (
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | shellPath = /bin/sh;
156 | shellScript = "cd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n";
157 | };
158 | /* End PBXShellScriptBuildPhase section */
159 |
160 | /* Begin PBXSourcesBuildPhase section */
161 | A93A953329CC810C00F8E227 /* Sources */ = {
162 | isa = PBXSourcesBuildPhase;
163 | buildActionMask = 2147483647;
164 | files = (
165 | A93A953B29CC810C00F8E227 /* iosApp.swift in Sources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXSourcesBuildPhase section */
170 |
171 | /* Begin XCBuildConfiguration section */
172 | A93A954329CC810D00F8E227 /* Debug */ = {
173 | isa = XCBuildConfiguration;
174 | buildSettings = {
175 | ALWAYS_SEARCH_USER_PATHS = NO;
176 | CLANG_ANALYZER_NONNULL = YES;
177 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
178 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
179 | CLANG_ENABLE_MODULES = YES;
180 | CLANG_ENABLE_OBJC_ARC = YES;
181 | CLANG_ENABLE_OBJC_WEAK = YES;
182 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
183 | CLANG_WARN_BOOL_CONVERSION = YES;
184 | CLANG_WARN_COMMA = YES;
185 | CLANG_WARN_CONSTANT_CONVERSION = YES;
186 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
187 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
188 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
189 | CLANG_WARN_EMPTY_BODY = YES;
190 | CLANG_WARN_ENUM_CONVERSION = YES;
191 | CLANG_WARN_INFINITE_RECURSION = YES;
192 | CLANG_WARN_INT_CONVERSION = YES;
193 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
194 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
195 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
196 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
197 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
198 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
199 | CLANG_WARN_STRICT_PROTOTYPES = YES;
200 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
201 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
202 | CLANG_WARN_UNREACHABLE_CODE = YES;
203 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
204 | COPY_PHASE_STRIP = NO;
205 | DEBUG_INFORMATION_FORMAT = dwarf;
206 | ENABLE_STRICT_OBJC_MSGSEND = YES;
207 | ENABLE_TESTABILITY = YES;
208 | GCC_C_LANGUAGE_STANDARD = gnu11;
209 | GCC_DYNAMIC_NO_PIC = NO;
210 | GCC_NO_COMMON_BLOCKS = YES;
211 | GCC_OPTIMIZATION_LEVEL = 0;
212 | GCC_PREPROCESSOR_DEFINITIONS = (
213 | "DEBUG=1",
214 | "$(inherited)",
215 | );
216 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
217 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
218 | GCC_WARN_UNDECLARED_SELECTOR = YES;
219 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
220 | GCC_WARN_UNUSED_FUNCTION = YES;
221 | GCC_WARN_UNUSED_VARIABLE = YES;
222 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
223 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
224 | MTL_FAST_MATH = YES;
225 | ONLY_ACTIVE_ARCH = YES;
226 | SDKROOT = iphoneos;
227 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
228 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
229 | };
230 | name = Debug;
231 | };
232 | A93A954429CC810D00F8E227 /* Release */ = {
233 | isa = XCBuildConfiguration;
234 | buildSettings = {
235 | ALWAYS_SEARCH_USER_PATHS = NO;
236 | CLANG_ANALYZER_NONNULL = YES;
237 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
238 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
239 | CLANG_ENABLE_MODULES = YES;
240 | CLANG_ENABLE_OBJC_ARC = YES;
241 | CLANG_ENABLE_OBJC_WEAK = YES;
242 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
243 | CLANG_WARN_BOOL_CONVERSION = YES;
244 | CLANG_WARN_COMMA = YES;
245 | CLANG_WARN_CONSTANT_CONVERSION = YES;
246 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
247 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
248 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
249 | CLANG_WARN_EMPTY_BODY = YES;
250 | CLANG_WARN_ENUM_CONVERSION = YES;
251 | CLANG_WARN_INFINITE_RECURSION = YES;
252 | CLANG_WARN_INT_CONVERSION = YES;
253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
257 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
258 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
259 | CLANG_WARN_STRICT_PROTOTYPES = YES;
260 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
261 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
262 | CLANG_WARN_UNREACHABLE_CODE = YES;
263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
264 | COPY_PHASE_STRIP = NO;
265 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
266 | ENABLE_NS_ASSERTIONS = NO;
267 | ENABLE_STRICT_OBJC_MSGSEND = YES;
268 | GCC_C_LANGUAGE_STANDARD = gnu11;
269 | GCC_NO_COMMON_BLOCKS = YES;
270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
272 | GCC_WARN_UNDECLARED_SELECTOR = YES;
273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
274 | GCC_WARN_UNUSED_FUNCTION = YES;
275 | GCC_WARN_UNUSED_VARIABLE = YES;
276 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
277 | MTL_ENABLE_DEBUG_INFO = NO;
278 | MTL_FAST_MATH = YES;
279 | SDKROOT = iphoneos;
280 | SWIFT_COMPILATION_MODE = wholemodule;
281 | SWIFT_OPTIMIZATION_LEVEL = "-O";
282 | VALIDATE_PRODUCT = YES;
283 | };
284 | name = Release;
285 | };
286 | A93A954629CC810D00F8E227 /* Debug */ = {
287 | isa = XCBuildConfiguration;
288 | buildSettings = {
289 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
290 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
291 | CODE_SIGN_STYLE = Automatic;
292 | CURRENT_PROJECT_VERSION = 1;
293 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
294 | ENABLE_PREVIEWS = YES;
295 | FRAMEWORK_SEARCH_PATHS = (
296 | "${inherited}",
297 | "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
298 | );
299 | GENERATE_INFOPLIST_FILE = YES;
300 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
301 | LD_RUNPATH_SEARCH_PATHS = (
302 | "$(inherited)",
303 | "@executable_path/Frameworks",
304 | );
305 | MARKETING_VERSION = 1.0;
306 | OTHER_LDFLAGS = (
307 | "${inherited}",
308 | "-framework",
309 | ComposeApp,
310 | );
311 | PRODUCT_BUNDLE_IDENTIFIER = org.company.app.iosApp;
312 | PRODUCT_NAME = "Crypto-KMP";
313 | SWIFT_EMIT_LOC_STRINGS = YES;
314 | SWIFT_VERSION = 5.0;
315 | TARGETED_DEVICE_FAMILY = "1,2";
316 | };
317 | name = Debug;
318 | };
319 | A93A954729CC810D00F8E227 /* Release */ = {
320 | isa = XCBuildConfiguration;
321 | buildSettings = {
322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
323 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
324 | CODE_SIGN_STYLE = Automatic;
325 | CURRENT_PROJECT_VERSION = 1;
326 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
327 | ENABLE_PREVIEWS = YES;
328 | FRAMEWORK_SEARCH_PATHS = (
329 | "${inherited}",
330 | "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
331 | );
332 | GENERATE_INFOPLIST_FILE = YES;
333 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
334 | LD_RUNPATH_SEARCH_PATHS = (
335 | "$(inherited)",
336 | "@executable_path/Frameworks",
337 | );
338 | MARKETING_VERSION = 1.0;
339 | OTHER_LDFLAGS = (
340 | "${inherited}",
341 | "-framework",
342 | ComposeApp,
343 | );
344 | PRODUCT_BUNDLE_IDENTIFIER = org.company.app.iosApp;
345 | PRODUCT_NAME = "Crypto-KMP";
346 | SWIFT_EMIT_LOC_STRINGS = YES;
347 | SWIFT_VERSION = 5.0;
348 | TARGETED_DEVICE_FAMILY = "1,2";
349 | };
350 | name = Release;
351 | };
352 | /* End XCBuildConfiguration section */
353 |
354 | /* Begin XCConfigurationList section */
355 | A93A953229CC810C00F8E227 /* Build configuration list for PBXProject "iosApp" */ = {
356 | isa = XCConfigurationList;
357 | buildConfigurations = (
358 | A93A954329CC810D00F8E227 /* Debug */,
359 | A93A954429CC810D00F8E227 /* Release */,
360 | );
361 | defaultConfigurationIsVisible = 0;
362 | defaultConfigurationName = Release;
363 | };
364 | A93A954529CC810D00F8E227 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
365 | isa = XCConfigurationList;
366 | buildConfigurations = (
367 | A93A954629CC810D00F8E227 /* Debug */,
368 | A93A954729CC810D00F8E227 /* Release */,
369 | );
370 | defaultConfigurationIsVisible = 0;
371 | defaultConfigurationName = Release;
372 | };
373 | /* End XCConfigurationList section */
374 | };
375 | rootObject = A93A952F29CC810C00F8E227 /* Project object */;
376 | }
377 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/iosApp.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import ComposeApp
3 |
4 | @main
5 | class AppDelegate: UIResponder, UIApplicationDelegate {
6 | var window: UIWindow?
7 |
8 | func application(
9 | _ application: UIApplication,
10 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
11 | ) -> Bool {
12 | window = UIWindow(frame: UIScreen.main.bounds)
13 | if let window = window {
14 | window.rootViewController = MainKt.MainViewController()
15 | window.makeKeyAndVisible()
16 | }
17 | return true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "Crypto-KMP"
2 | include(":composeApp")
3 |
4 | pluginManagement {
5 | repositories {
6 | google()
7 | gradlePluginPortal()
8 | mavenCentral()
9 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
10 | maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental")
11 | maven( "https://androidx.dev/storage/compose-compiler/repository")
12 | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
13 | }
14 | }
15 |
16 | dependencyResolutionManagement {
17 | repositories {
18 | google()
19 | mavenCentral()
20 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
21 | maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental")
22 | maven( "https://androidx.dev/storage/compose-compiler/repository")
23 | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------